/*--------------------------------------------------------------*/
/* vesta.c ---                                                  */
/*                                                              */
/*      This file reads two files:  (1) A verilog netlist file  */
/*      of a circuit (structural verilog, that is, gate-level   */
/*      information only), and (2) a liberty format file        */
/*      containing timing information for the standard cell     */
/*      macros instantiated in the verilog netlist.  In         */
/*      addition, it may take an optional third file containing */
/*      information about the resistance and capacitance of the */
/*      wiring (see below).                                     */
/*                                                              */
/*      Options are supplied as command-line arguments:         */
/*                                                              */
/*              -d <delay_file> Wiring delays (see below)       */
/*              -p <value>      Clock period, in ps             */
/*              -l <value>      Output load, in fF              */
/*              -v <level>      set verbose mode                */
/*              -D <level>      set debug mode                  */
/*              -V              report version number           */
/*		-n <number>	number of paths to print	*/
/*		-L 		Long format (print paths)	*/
/*              -e              exhaustive search               */
/*		-s <file>	summary file or directory	*/
/*		-c		cleanup of net name syntax	*/
/*                                                              */
/*      Currently the only output this tool generates is a      */
/*      list of paths with negative slack.  If no paths have    */
/*      negative slack, then the 20 paths with the smallest     */
/*      positive slack are output, following a "success"        */
/*      message.  If no clock period is supplied, then the      */
/*      clock period is set to equal the delay of the longest   */
/*      delay path, and the 20 paths with the smallest positive */
/*      slack are output, following a statement indicated the   */
/*      computed minimum clock period.                          */
/*								*/
/*	Debug level (developer diagnostics):			*/
/*	    Not cumulative.  Each number does something unique.	*/
/*		1:  Print delay file parsing information	*/
/*		2:  Print liberty file parsing information	*/
/*								*/
/*	Verbose level (user diagnostics):			*/
/*	    Cumulative.  The higher the number, the more	*/
/* 	    output is generated.				*/
/*		1: Report on total lines processed.		*/
/*		2: Report on each path processed.		*/
/*		3: Report on verilog source file		*/
/*		4: Report on liberty file			*/
/*--------------------------------------------------------------*/
/*	(c) 2013-2019 Tim Edwards, Open Circuit Design		*/
/*	Released under GPL as part of the qflow package		*/
/*--------------------------------------------------------------*/

/*--------------------------------------------------------------*/
/*      Wiring delay file:                                      */
/*      For qflow, the wiring delay is generated by the tool    */
/*      "def2delays".  The file format is as follows:           */
/*                                                              */
/*      <net_name>                                              */
/*      <output_terminal>  <net_capacitance>                    */
/*      <input_terminal_1> <delay_1>                            */
/*      ...                                                     */
/*      <input_terminal_N> <delay_N>                            */
/*                                                              */
/*      -<net_capacitance> is in pF                             */
/*      -Values <delay_i> are in ps                             */
/*      -<input_terminal_N> line *must* be following by a blank */
/*       line                                                   */
/*--------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <sys/types.h>	// For mkdir()
#include <sys/stat.h>	// For mkdir()
#include <math.h>       // Temporary, for fabs()
#include "hash.h"       // For net hash table
#include "readverilog.h"

#define LIB_LINE_MAX  65535

#ifdef __APPLE__
// Linux defines a comparison function prototype, the Mac doesn't. . .
typedef int (*__compar_fn_t)(const void *, const void *);
#endif

int fileCurrentLine;

// Analysis types --- note that maximum flop-to-flop delay
// requires calculating minimum clock skew time, and vice
// versa, so it is necessary that these have TRUE/FALSE
// values.

#define MINIMUM_TIME    0
#define MAXIMUM_TIME    1

#define INITVAL		-1.0E50		/* Value to detect uninitialized delay */

// Multiple-use definition
#define UNKNOWN         -1

// Sections of liberty file
#define INIT            0
#define LIBBLOCK        1
#define CELLDEF         2
#define PINDEF          3
#define FLOPDEF         4
#define LATCHDEF        5
#define TIMING          6

// Sections of verilog file
#define MODULE          0
#define IOINLINE        1
#define IOLIST          2
#define GATELIST        3
#define INSTANCE        4
#define INSTPIN         5
#define PINCONN         6

// Pin types (these are masks---e.g., a pin can be an INPUT and a CLOCK)
#define INPUT           0x01    // The default
#define OUTPUT          0x02
#define DFFCLK          0x04
#define DFFIN           0x08    // Flop input
#define DFFOUT          0x10    // Flop output
#define DFFSET          0x20    // Flop set (preset)
#define DFFRST          0x40    // Flop reset (clear)
#define LATCHIN         0x80    // Latch input
#define LATCHEN         0x100   // Latch enable
#define LATCHOUT        0x200   // Latch output

// Timing type (for tables)

#define TIMING_PROP_TRANS       0
#define TIMING_HOLD             1
#define TIMING_SETUP            2
#define TIMING_SET_RESET        3
#define TIMING_RECOVERY         4
#define TIMING_REMOVAL          5
#define TIMING_TRISTATE         6

// A few short-hand definitions
#define DFF_ALL_IN      (DFFCLK | DFFIN | DFFSET | DFFRST)
#define LATCH_ALL_IN    (LATCHIN | LATCHEN)

#define DFF_IN_NOT_CLK  (DFFIN | DFFSET | DFFRST)
#define LATCH_IN_NOT_EN (LATCHIN)

#define REGISTER_IN     (DFF_ALL_IN | LATCH_ALL_IN)
#define REG_IN_NOT_CLK  (DFF_IN_NOT_CLK | LATCH_IN_NOT_EN)

#define IOMASK          0x03
#define DFFMASK         0x7c
#define LATCHMASK       0x180

// Pin sense
#define SENSE_UNKNOWN   0       // Sense unknown
#define SENSE_NONE      1       // Non-unate
#define SENSE_POSITIVE  2       // Positive-unate
#define SENSE_NEGATIVE  3       // Negative-unate

// Signal transition direction
#define EDGE_UNKNOWN    0
#define RISING          1
#define FALLING         2
#define EITHER          3

// Function translation
#define GROUPBEGIN      1
#define GROUPEND        2
#define SIGNAL          3
#define OPERATOR        4
#define XOPERATOR       5
#define SEPARATOR       6

// Net types
#define NET             0x00    // Ordinary net (default)
#define CLOCK           0x01    // Clock net (path start)
#define OUTTERM         0x02    // Module output
#define ASYNC           0x04    // Asynchronous set/reset
#define TERMINAL        0x08    // DFF input (path terminal)
#define LATCHTERM       0x10    // Latch input (dependent path terminal)
#define ENABLE          0x20    // Latch enable (path start)

// Cell types
#define GATE            0x00    // Combinatorial gate (default)
#define DFF             0x01    // Flip-flop (shared bit field)
#define CLK_SENSE_MASK  0x02    // Clock edge mask (0=positive, 1=negative)
#define RST_MASK        0x04    // Reset mask (0=no reset, 1=reset)
#define RST_SENSE_MASK  0x08    // Reset edge mask (0=positive, 1=negative)
#define SET_MASK        0x10    // Set mask (0=no set, 1=set)
#define SET_SENSE_MASK  0x20    // Set edge mask (0=positive, 1=negative)
#define LATCH           0x40    // Latch type
#define EN_SENSE_MASK   0x80    // Latch enable edge mask (0=positive, 1=negative)

// Some names for cell types based on masks

#define DFFCP           0x01    // Pos clock
#define DFFCN           0x03    // Neg clock
#define DFFCPRP         0x05    // Pos clock, Pos reset
#define DFFCNRP         0x07    // Neg clock, Pos reset
#define DFFCPRN         0x0d    // Pos clock, Neg reset
#define DFFCNRN         0x0f    // Neg clock, Neg reset
#define DFFCPSP         0x11    // Pos clock, Pos set
#define DFFCNSP         0x13    // Neg clock, Pos set
#define DFFCPRPSP       0x15    // Pos clock, Pos reset, Pos set
#define DFFCNRPSP       0x17    // Neg clock, Pos reset, Pos set
#define DFFCPRNSP       0x1d    // Pos clock, Neg reset, Pos set
#define DFFCNRNSP       0x1f    // Neg clock, Neg reset, Pos set
#define DFFCPSN         0x31    // Pos clock, Neg set
#define DFFCNSN         0x33    // Neg clock, Neg set
#define DFFCPRPSN       0x35    // Pos clock, Pos reset, Neg set
#define DFFCNRPSN       0x37    // Neg clock, Pos reset, Neg set
#define DFFCPRNSN       0x3d    // Pos clock, Neg reset, Neg set
#define DFFCNRNSN       0x3f    // Neg clock, Neg reset, Neg set

/*--------------------------------------------------------------*/
/* Liberty file database                                        */
/*--------------------------------------------------------------*/

// Types of array

// Array type 1:  Propagation time
#define OUTPUT_CAP              0
#define TRANSITION_TIME         1

// Array type 2:  Setup, hold, recovery, removal times
#define RELATED_TIME            2
#define CONSTRAINED_TIME        3

typedef struct _lutable *lutableptr;

typedef struct _lutable {
    char *name;
    char invert;        // 0 if times x caps, 1 if caps x times
    int  var1;          // Type of array in index1
    int  var2;          // Type of array in index2
    int  size1;         // Number of entries in time array
    int  size2;         // Number of entries in cap (or constrained timing) array
    union {
        double *times;  // Time array (units ps)
        double *rel;    // Related pin transition time array (units ps)
    } idx1;
    union {
        double *caps;   // Cap array (units fF)
        double *cons;   // Constrained pin transition time array (units ps)
    } idx2;
    double *values;     // Matrix of values (used locally, not for templates)
    lutableptr next;
} lutable;

typedef struct _bustype *bustypeptr;
typedef struct _pin *pinptr;
typedef struct _cell *cellptr;

typedef struct _bustype {
    char *name;
    int   from;
    int   to;
    bustypeptr next;
} bus;

typedef struct _pin {
    char *name;
    short type;

    double capr;        // Capacitance for rising input
    double capf;        // Capacitance for falling input (optional)

    short sense;        // Sense (positive-unate, negative-unate, non-unate)
    lutable *propdelr;  // Reference table for rising output prop delay relative to driver
    lutable *propdelf;  // Reference table for falling output prop delay relative to driver
    lutable *transr;    // Reference table for transition rise time
    lutable *transf;    // Reference table for transition fall time

    cellptr refcell;    // Pointer back to parent cell

    pinptr next;
} pin;

typedef struct _cell {
    short type;
    char *name;
    char *function;
    pin  *pins;         /* List of input pins with timing info */
    double area;
    double maxtrans;    /* Maximum transition time */
    double maxcap;      /* Maximum allowable load */
    cellptr next;
} cell;

/*--------------------------------------------------------------*/
/* Verilog netlist database                                     */
/*--------------------------------------------------------------*/

typedef struct _net *netptr;
typedef struct _connect *connptr;
typedef struct _delaydata *ddataptr;

typedef struct _net {
   char *name;
   connptr driver;
   short type;
   int fanout;
   connptr *receivers;
   double loadr;        /* Total load capacitance for rising input */
   double loadf;        /* Total load capacitance for falling input */
   netptr next;
} net;

typedef struct _instance *instptr;

typedef struct _connect {
   double   metric;             /* Delay metric at connection */
   double   icDelay;            /* interconnect delay in ps */
   instptr  refinst;
   pinptr   refpin;
   netptr   refnet;
   unsigned char visited;	/* To check for common clock points */
   ddataptr tag;                /* Tag value for checking for loops and endpoints */
   double   *prvector;          /* Prop delay rising (at load condition) vector */
   double   *pfvector;          /* Prop delay falling (at load condition) vector */
   double   *trvector;          /* Transition time rising (at load condition) vector */
   double   *tfvector;          /* Transition time falling (at load condition) vector */
   connptr  next;
} connect;

typedef struct _instance {
   char *name;
   cellptr refcell;
   connptr in_connects;
   connptr out_connects;
   instptr next;
} instance;

// Linked list of delays (backtrace to source)

typedef struct _btdata *btptr;

typedef struct _btdata {
   double  delay;       /* Propagation delay to this point */
   double  trans;       /* Transition time at this point */
   short   dir;         /* Edge direction at this point */
   connptr receiver;    /* Receiver connection at end of path */
   int     refcnt;      /* Reference counter for backtrace data */
   btptr   next;        /* Path of propagation */
} btdata;

// Linked list of backtrace records

typedef struct _delaydata {
   double delay;        /* Total delay, including setup and clock skew */
   double skew;		/* Part of total delay attributed to clock skew */
   double setup;	/* Part of total delay attributed to setup (+) or hold (-) */
   double trans;        /* Transition time at destination, used to find setup */
   btptr backtrace;
   ddataptr  next;
} delaydata;

// Linked list of connection pointers
// (Much like delaydata, but without all the timing information)

typedef struct _connlist *connlistptr;

typedef struct _connlist {
   connptr connection;
   connlistptr next;
} connlist;

/* Global variables */

unsigned char verbose;       /* Level of user output generated */
unsigned char debug;	     /* Level of debug output generated */
unsigned char exhaustive;    /* Exhaustive search mode */
unsigned char cleanup;       /* Clean up net name syntax */

/*--------------------------------------------------------------*/
/* Grab a token from the input                                  */
/* Return the token, or NULL if we have reached end-of-file.    */
/*--------------------------------------------------------------*/

char *
advancetoken0(FILE *flib, char delimiter, char nocontline)
{
    static char *token = NULL;
    static char line[LIB_LINE_MAX];
    static char *linepos = NULL;
    static int token_max_length = LIB_LINE_MAX - 5;

    char *lineptr = linepos;
    char *lptr, *tptr;
    char *result;
    int commentblock, concat, nest;

    if (token == NULL) token = (char *)malloc(LIB_LINE_MAX);

    commentblock = 0;
    concat = 0;
    nest = 0;
    while (1) {         /* Keep processing until we get a token or hit EOF */

        if (lineptr != NULL && *lineptr == '/' && *(lineptr + 1) == '*') {
            commentblock = 1;
        }

        if (commentblock == 1) {
            if ((lptr = strstr(lineptr, "*/")) != NULL) {
                lineptr = lptr + 2;
                commentblock = 0;
            }
            else lineptr = NULL;
        }

	// Sloppy liberty spec was supposed to have ';' terminating
	// lines but it allows them to go missing.  Fix this by adding
	// semicolons to statements missing them.

	if (lineptr && *lineptr == '\n' && delimiter == ';') *lineptr = ';';

        if (lineptr == NULL || *lineptr == '\n' || *lineptr == '\0') {
            result = fgets(line, LIB_LINE_MAX, flib);
            fileCurrentLine++;
	    if (verbose > 0) {
		if ((fileCurrentLine % 10000) == 0) {
		    fprintf(stdout, "Processed %d lines.\n", fileCurrentLine);
		    fflush(stdout);
		}
	    }
            if (result == NULL) return NULL;

            /* Keep pulling stuff in if the line ends with a continuation character */
            lptr = line;
            while (*lptr != '\n' && *lptr != '\0') {
                if ((*lptr == '\\') && (nocontline == 0)) {
                    // To be considered a line continuation marker, there must be
                    // only whitespace or newline between the backslash and the
                    // end of the string.
                    char *eptr = lptr + 1;
                    while (isblank(*eptr)) eptr++;
                    if (*eptr == '\n') {
                        result = fgets(lptr, LIB_LINE_MAX - (lptr - line), flib);
                        fileCurrentLine++;
                        if (result == NULL) break;
                    }
                    else
                        lptr++;
                }
                else
                    lptr++;
            }
            if (result == NULL) return NULL;
            lineptr = line;
        }

        if (commentblock == 1) continue;

        while (isblank(*lineptr)) lineptr++;
        if (concat == 0)
            tptr = token;

        // Find the next token and return just the token.  Update linepos
        // to the position just beyond the token.  All delimiters like
        // parentheses, quotes, etc., are returned as single tokens

        // If delimiter is declared, then we stop when we reach the
        // delimiter character, and return all the text preceding it
        // as the token.  If delimiter is 0, then we look for standard
        // delimiters, and separate them out and return them as tokens
        // if found.

        while (1) {
            if (*lineptr == '\n' || *lineptr == '\0')
                break;
            if (*lineptr == '/' && *(lineptr + 1) == '*')
                break;
            if (delimiter != 0 && *lineptr == delimiter) {
                if (nest > 0)
                    nest--;
                else
                    break;
            }

	    // Watch for overruns, and allocate more memory
	    // for the token if needed.

	    if ((tptr - token) > token_max_length) {
		char *tsave = token;
		token_max_length <<= 1;
		token = (char *)realloc(token, token_max_length);
		tptr += (token - tsave);
	    }

            // Watch for nested delimiters!
            if (delimiter == '}' && *lineptr == '{') nest++;
            if (delimiter == ')' && *lineptr == '(') nest++;

            if (delimiter == 0)
                if (*lineptr == ' ' || *lineptr == '\t')
                    break;

            if (delimiter == 0) {
                if (*lineptr == '(' || *lineptr == ')') {
                    if (tptr == token) *tptr++ = *lineptr++;
                    break;
                }
                if (*lineptr == '{' || *lineptr == '}') {
                    if (tptr == token) *tptr++ = *lineptr++;
                    break;
                }
                if (*lineptr == '\"' || *lineptr == ':' || *lineptr == ';') {
                    if (tptr == token) *tptr++ = *lineptr++;
                    break;
                }
            }

            *tptr++ = *lineptr++;
        }
        *tptr = '\0';
        if ((delimiter != 0) && (*lineptr != delimiter))
            concat = 1;
        else if ((delimiter != 0) && (*lineptr == delimiter))
            break;
        else if (tptr > token)
            break;
    }
    if (delimiter != 0) lineptr++;

    while (isblank(*lineptr)) lineptr++;
    linepos = lineptr;

    // Final:  Remove trailing whitespace
    tptr = token + strlen(token) - 1;
    while ((tptr > token) && isblank(*tptr)) {
        *tptr = '\0';
        tptr--;
    }
    return token;
}

/*--------------------------------------------------------------*/
/* Wrapper for advancetoken0():  Default nocontline = 0		*/
/*--------------------------------------------------------------*/

char *
advancetoken(FILE *flib, char delimiter)
{
    return advancetoken0(flib, delimiter, 0);
}

/*--------------------------------------------------------------*/
/* Wrapper for advancetoken0():  nocontline = 1	for delay file	*/
/*--------------------------------------------------------------*/

char *
advancetokennocont(FILE *flib, char delimiter)
{
    return advancetoken0(flib, delimiter, 1);
}

/*--------------------------------------------------------------*/
/* Parse a pin name.  Check if the cell has a pin of that name, */
/* and if not, add the pin to the cell, giving it default       */
/* values.  The pin name may contain quotes, parentheses, or    */
/* negations ("!" or "'");  these should be ignored.            */
/*--------------------------------------------------------------*/

pinptr parse_pin(cellptr newcell, char *token, short sense_predef)
{
    pinptr newpin, lastpin;
    char *pinname, *sptr;

    // Advance to first legal pin name character

    pinname = token;
    while (isblank(*pinname) || (*pinname == '\'') || (*pinname == '\"') ||
                (*pinname == '!') || (*pinname == '(') || (*pinname == ')'))
        pinname++;

    sptr = pinname;
    while (*sptr != '\0') {
        if (isblank(*sptr) || (*sptr == '\'') || (*sptr == '\"') ||
                (*sptr == '!') || (*sptr == '(') || (*sptr == ')')) {
            *sptr = '\0';
            break;
        }
        sptr++;
    }

    // Check if pin was already defined

    lastpin = NULL;
    newpin = newcell->pins;
    while (newpin) {
        lastpin = newpin;
        if (!strcmp(newpin->name, pinname))
            return newpin;
        newpin = newpin->next;
    }

    // Pin was not defined, so create a new one and add it to the cell
    // at the end of the cell's pin list.

    newpin = (pin *)malloc(sizeof(pin));
    newpin->name = strdup(pinname);
    newpin->next = NULL;

    if (lastpin != NULL)
        lastpin->next = newpin;
    else
        newcell->pins = newpin;

    newpin->type = INPUT;       // The default; modified later, if not an input
    newpin->capr = 0.0;
    newpin->capf = 0.0;
    newpin->sense = sense_predef;  // Again, modified later if not true.
    newpin->propdelr = NULL;
    newpin->propdelf = NULL;
    newpin->transr = NULL;
    newpin->transf = NULL;
    newpin->refcell = newcell;  // Create link back to cell
    return newpin;
}

/*--------------------------------------------------------------*/
/* Create a new net record                                      */
/*--------------------------------------------------------------*/

netptr create_net(netptr *netlist) {

    netptr newnet;

    newnet = (netptr)malloc(sizeof(net));
    newnet->name = NULL;
    newnet->next = *netlist;
    *netlist = newnet;
    newnet->driver = NULL;
    newnet->fanout = 0;
    newnet->receivers = NULL;
    newnet->loadr = 0.0;
    newnet->loadf = 0.0;
    newnet->type = NET;

    return newnet;
}

/*----------------------------------------------------------------------*/
/* Interpolate or extrapolate a vector from a time vs. capacitance      */
/* lookup table.                                                        */
/*----------------------------------------------------------------------*/

double *table_collapse(lutableptr tableptr, double load)
{
    double *vector;
    double cfrac, vlow, vhigh;
    int i, j;

    vector = (double *)malloc(tableptr->size1 * sizeof(double));

    // If the table is 1-dimensional, then just return a copy of the table.
    if (tableptr->size2 <= 1) {
       for (i = 0; i < tableptr->size1; i++) {
          *(vector + i) = *(tableptr->values + i);
       }
       return vector;
    }

    // Find cap load index entries bounding  "load", or the two nearest
    // entries, if extrapolating

    if (load < tableptr->idx2.caps[0])
        j = 1;
    else if (load >= tableptr->idx2.caps[tableptr->size2 - 1])
        j = tableptr->size2 - 1;
    else {
        for (j = 0; j < tableptr->size2; j++)
            if (tableptr->idx2.caps[j] > load)
                break;
    }

    cfrac = (load - tableptr->idx2.caps[j - 1]) /
                        (tableptr->idx2.caps[j] - tableptr->idx2.caps[j - 1]);

    for (i = 0; i < tableptr->size1; i++) {

        // Interpolate value at cap load for each transition value

        vlow = *(tableptr->values + i * tableptr->size2 + (j - 1));
        vhigh = *(tableptr->values + i * tableptr->size2 + j);
        *(vector + i) = vlow + (vhigh - vlow) * cfrac;
    }
    return vector;
}

/*----------------------------------------------------------------------*/
/* Interpolate/extrapolate a delay or transition value from a vector of */
/* values at a known output load.  The original full 2D table contains  */
/* the transition time index values.                                    */
/*----------------------------------------------------------------------*/

double vector_get_value(lutableptr tableptr, double *vector, double trans)
{
    int i;
    double tfrac, vlow, vhigh, value;

    // Find time index entries bounding  "trans", or the two nearest
    // entries, if extrapolating

    if (trans < tableptr->idx1.times[0])
        i = 1;
    else if (trans >= tableptr->idx1.times[tableptr->size1 - 1])
        i = tableptr->size1 - 1;
    else {
        for (i = 0; i < tableptr->size1; i++)
            if (tableptr->idx1.times[i] > trans)
                break;
    }

    // Compute transition time as a fraction of the nearest table indexes
    // for transition times

    tfrac = (trans - tableptr->idx1.times[i - 1]) /
                (tableptr->idx1.times[i] - tableptr->idx1.times[i - 1]);

    // Interpolate value
    vlow = *(vector + (i - 1));
    vhigh = *(vector + i);
    value = vlow + (vhigh - vlow) * tfrac;
    return value;
}

/*----------------------------------------------------------------------*/
/* Interpolate or extrapolate a value from a related time vs.           */
/* constrained time lookup table.                                       */
/*----------------------------------------------------------------------*/

double binomial_get_value(lutableptr tableptr, double rtrans, double ctrans)
{
    int i, j;
    double rfrac, cfrac, vlow, vhigh, valuel, valueh, value;

    /* Tables have been arranged such that idx1 is related time,        */
    /* idx2 is constrained time */

    // Find time index entries bounding  "rtrans", or the two nearest
    // entries, if extrapolating

    if (rtrans < tableptr->idx1.rel[0])
        i = 1;
    else if (rtrans >= tableptr->idx1.rel[tableptr->size1 - 1])
        i = tableptr->size1 - 1;
    else {
        for (i = 0; i < tableptr->size1; i++)
            if (tableptr->idx1.rel[i] > rtrans)
                break;
    }

    // Compute transition time as a fraction of the nearest table indexes
    // for transition times

    rfrac = (rtrans - tableptr->idx1.rel[i - 1]) /
                (tableptr->idx1.rel[i] - tableptr->idx1.rel[i - 1]);

    // 1-dimensional computation, if this table is 1-dimensional

    if (tableptr->size2 == 0) {
        vlow = *(tableptr->values + (i - 1));
        vhigh = *(tableptr->values + i);
        value = vlow + (vhigh - vlow) * rfrac;
        return value;
    }

    // Find cons index entries bounding  "ctrans", or the two nearest
    // entries, if extrapolating

    if (ctrans < tableptr->idx2.cons[0])
        j = 1;
    else if (ctrans >= tableptr->idx2.cons[tableptr->size2 - 1])
        j = tableptr->size2 - 1;
    else {
        for (j = 0; j < tableptr->size2; j++)
            if (tableptr->idx2.cons[j] > ctrans)
                break;
    }

    // Compute cons transition as a fraction of the nearest table indexes for cons

    cfrac = (ctrans - tableptr->idx2.cons[j - 1]) /
                        (tableptr->idx2.cons[j] - tableptr->idx2.cons[j - 1]);

    // Interpolate value at cons lower bound
    vlow = *(tableptr->values + (i - 1) * tableptr->size1 + (j - 1));
    vhigh = *(tableptr->values + i * tableptr->size1 + (j - 1));
    valuel = vlow + (vhigh - vlow) * rfrac;

    // Interpolate value at cons upper bound
    vlow = *(tableptr->values + (i - 1) * tableptr->size1 + j);
    vhigh = *(tableptr->values + i * tableptr->size1 + j);
    valueh = vlow + (vhigh - vlow) * rfrac;

    // Final interpolation (binomial interpolation)
    value = valuel + (valueh - valuel) * cfrac;
    return value;
}

/*----------------------------------------------------------------------*/
/* Determine how the sense of a signal changes going from a gate's      */
/* input to its output.  If the gate's input pin is positive unate      */
/* relative to the gate output, then the signal sense remains the same. */
/* If it is negative unate, then the signal sense inverts.  If it is    */
/* non-unate, then the signal sense becomes non-unate, and we calculate */
/* timing for both edges from that point forward, always accepting the  */
/* maximum time.                                                        */
/*----------------------------------------------------------------------*/

short calc_dir(pinptr testpin, short dir)
{
    short outdir;

    outdir = UNKNOWN;
    if (testpin == NULL) return dir;

    switch(dir) {
        case RISING:
            if (testpin->sense == SENSE_POSITIVE)
                outdir = RISING;        /* rising input, rising output */
            else if (testpin->sense == SENSE_NEGATIVE)
                outdir = FALLING;       /* rising input, falling output */
            else
                outdir = EITHER;        /* output can be rising or falling */
            break;
        case FALLING:
            if (testpin->sense == SENSE_POSITIVE)
                outdir = FALLING;       /* falling input, falling output */
            else if (testpin->sense == SENSE_NEGATIVE)
                outdir = RISING;        /* falling input, rising output */
            else
                outdir = EITHER;                /* output can be rising or falling */
            break;
        case EITHER:
            outdir = EITHER;            /* output can be rising or falling */
            break;
    }
    return outdir;
}

/*----------------------------------------------------------------------*/
/* Calculate the propagation delay from "testpin" to the output         */
/* of the gate to which "testpin" is an input.                          */
/*                                                                      */
/* "sense" is a pointer to the sense at the input.  SENSE_POSITIVE      */
/* indicates a rising edge at the pin, SENSE_NEGATIVE indicates a       */
/* falling edge at the pin.  "sense" is updated to indicate if the      */
/* output transition is rising, falling, or unknown (SENSE_NONE).       */
/*                                                                      */
/* "loadnet" is a pointer to the net connected to the cell instance's   */
/* output pin.  Load values will be taken from this net, depending on   */
/* the sense of the output.                                             */
/*                                                                      */
/* "testpin" is the pin receiving the input signal, and the pin record  */
/* containing the relevant timing tables.                               */
/*----------------------------------------------------------------------*/

double calc_prop_delay(double trans, connptr testconn, short sense, char minmax)
{
    pinptr testpin;
    double propdelayr, propdelayf;

    propdelayr = 0.0;
    propdelayf = 0.0;

    testpin = testconn->refpin;
    if (testpin == NULL) return 0.0;

    if (sense != SENSE_NEGATIVE) {
        if (testconn->prvector)
            propdelayr = vector_get_value(testpin->propdelr, testconn->prvector, trans);
	if (propdelayr < 0.0) propdelayr = 0.0;
        if (sense == SENSE_POSITIVE) return propdelayr;
    }

    if (sense != SENSE_POSITIVE) {
        if (testconn->pfvector)
            propdelayf = vector_get_value(testpin->propdelf, testconn->pfvector, trans);
	if (propdelayf < 0.0) propdelayf = 0.0;
        if (sense == SENSE_NEGATIVE) return propdelayf;
    }

    if (minmax == MAXIMUM_TIME)
        return (propdelayr > propdelayf) ? propdelayr : propdelayf;
    else
        return (propdelayr < propdelayf) ? propdelayr : propdelayf;
}

/*----------------------------------------------------------------------*/
/* Calculate the transition time from "testpin" to the output           */
/* of the gate to which "testpin" is an input.  This is equivalent to   */
/* the propagation delay calculation routine above, apart from using    */
/* the lookup tables for transition time instead of propagation delay.  */
/*----------------------------------------------------------------------*/

double calc_transition(double trans, connptr testconn, short sense, char minmax)
{
    pinptr testpin;
    double transr, transf;

    testpin = testconn->refpin;
    if (testpin == NULL) return 0.0;

    transr = 0.0;
    transf = 0.0;

    if (sense != SENSE_NEGATIVE) {
        if (testconn->trvector)
            transr = vector_get_value(testpin->transr, testconn->trvector, trans);
	if (transr < 0.0) transr = 0.0;
        if (sense == SENSE_POSITIVE) return transr;
    }

    if (sense != SENSE_POSITIVE) {
        if (testconn->tfvector)
            transf = vector_get_value(testpin->transf, testconn->tfvector, trans);
	if (transf < 0.0) transf = 0.0;
        if (sense == SENSE_NEGATIVE) return transf;
    }

    if (minmax == MAXIMUM_TIME)
        return (transr > transf) ? transr : transf;
    else
        return (transr < transf) ? transr : transf;
}

/*----------------------------------------------------------------------*/
/* Calculate the hold time for a flop input "testpin" relative to the   */
/* flop clock, where "trans" is the transition time of the signal at    */
/* "testpin", and "clktrans" is the transition time of the clock        */
/* signal at the clock pin.  "sense" is the sense of the input signal   */
/* at "testpin".                                                        */
/*----------------------------------------------------------------------*/

double calc_hold_time(double trans, pinptr testpin, double clktrans, short sense,
                char minmax)
{
    double holdr, holdf;

    if (testpin == NULL) return 0.0;

    holdr = 0.0;
    holdf = 0.0;

    if (sense != SENSE_NEGATIVE) {
        if (testpin->transr)
            holdr = binomial_get_value(testpin->transr, trans, clktrans);
        if (sense == SENSE_POSITIVE) return holdr;
    }

    if (sense != SENSE_POSITIVE) {
        if (testpin->transf)
            holdf = binomial_get_value(testpin->transf, trans, clktrans);
        if (sense == SENSE_NEGATIVE) return holdf;
    }

    if (minmax == MAXIMUM_TIME)
        return (holdr < holdf) ? holdr : holdf;
    else
        return (holdr > holdf) ? holdr : holdf;
}

/*----------------------------------------------------------------------*/
/* Calculate the setup time for a flop input "testpin" relative to the  */
/* flop clock, where "trans" is the transition time of the signal at    */
/* "testpin", and "clktrans" is the transition time of the clock        */
/* signal at the clock pin.  "sense" is the sense of the input signal   */
/* at "testpin".                                                        */
/*----------------------------------------------------------------------*/

double calc_setup_time(double trans, pinptr testpin, double clktrans, short sense,
                char minmax)
{
    double setupr, setupf;

    if (testpin == NULL) return 0.0;

    setupr = 0.0;
    setupf = 0.0;

    if (sense != SENSE_NEGATIVE) {
        if (testpin->propdelr)
            setupr = binomial_get_value(testpin->propdelr, trans, clktrans);
        if (sense == SENSE_POSITIVE) return setupr;
    }

    if (sense != SENSE_POSITIVE) {
        if (testpin->propdelf)
            setupf = binomial_get_value(testpin->propdelf, trans, clktrans);
        if (sense == SENSE_NEGATIVE) return setupf;
    }

    if (minmax == MAXIMUM_TIME)
        return (setupr > setupf) ? setupr : setupf;
    else
        return (setupr < setupf) ? setupr : setupf;
}

/*--------------------------------------------------------------*/
/* Find the path from a clock back to all inputs or flop        */
/* outputs.  This list will be used to find nodes that are      */
/* common to other clocks.                                      */
/*								*/
/* For each pair of registers involved in a delay path		*/
/* calculation, this routine is called once for both devices.	*/
/* Set mode = 0 for the first device, and the connections are	*/
/* marked when found.  Set mode = 1 for the second device, and	*/
/* the search stops when a marked connection is found---this is	*/
/* the common clock net between the devices.  If no common	*/
/* clock is found, then return NULL---these endpoints are	*/
/* asynchronously clocked.					*/
/*								*/
/* Modified 5/23/2019:  Cascaded clocks should be traced	*/
/* through a flop.  i.e., if a clock is found to come from a	*/
/* DFF output, then add the clock-to-Q delay and keep tracing	*/
/* from the DFF clock.						*/
/*								*/
/* If mode == 2, return TRUE if the search ended on a		*/
/* connection found in a mode 1 search.  Otherwise, return	*/
/* FALSE.							*/
/*--------------------------------------------------------------*/

unsigned char
find_clock_source(connptr testlink, ddataptr *clocklist, btptr btrace, short dir,
		unsigned char mode)
{
    netptr clknet;
    connptr driver, iinput;
    instptr iupstream;
    btptr newclock;
    ddataptr newdataptr;
    short newdir;
    unsigned char result = 0;

    /* Add this connection record to the backtrace */

    newclock = (btptr)malloc(sizeof(btdata));
    newclock->delay = -1.0E50;	/* Initialization constant */
    newclock->trans = 0.0;
    newclock->dir = dir;
    newclock->refcnt = 0;
    newclock->receiver = testlink;
    newclock->next = btrace;
    if (btrace) btrace->refcnt++;

    /* On mode 2, stop when a node marked 1 is reached. */
    if ((mode == (unsigned char)2) && (testlink->visited == (unsigned char)1)) {
	result = (unsigned char)1;
	goto makehead;
    }

    /* Mark connection as visited */
    testlink->visited = mode;

    clknet = testlink->refnet;
    driver = clknet->driver;
    if (driver == NULL) goto makehead;		/* Reached a module input */
    iupstream = driver->refinst;

    if (iupstream == NULL) goto makehead;         /* Not supposed to happen? */

    /* If a flop or latch output is reached, add the clock-to-output delay  */
    /* and continue tracing from the DFF clock or LATCH input.		    */

    if (driver->refpin->type & DFFOUT) {
	for (iinput = driver->refinst->in_connects; iinput; iinput = iinput->next) {
	    if (iinput->refpin->type & DFFCLK) {
		newdir = calc_dir(iinput->refpin, dir);
		result = find_clock_source(iinput, clocklist, newclock, newdir, mode);
		if (result == (unsigned char)1) return result;
	    }
	}
    }
    else if (driver->refpin->type & LATCHOUT) {
	for (iinput = driver->refinst->in_connects; iinput; iinput = iinput->next) {
	    if (iinput->refpin->type & LATCHIN) {
		newdir = calc_dir(iinput->refpin, dir);
		result = find_clock_source(iinput, clocklist, newclock, newdir, mode);
		if (result == (unsigned char)1) return result;
	    }
	}
    }
    else {
	for (iinput = iupstream->in_connects; iinput; iinput = iinput->next) {
	    newdir = calc_dir(iinput->refpin, dir);
	    result = find_clock_source(iinput, clocklist, newclock, newdir, mode);
	    if (result == (unsigned char)1) return result;
	}
    }
    return (unsigned char)0;
    
makehead:
    /* Reached the head of a clock tree, so save the position	*/
    /* in the list of backtraces.				*/

    newdataptr = (ddataptr)malloc(sizeof(delaydata));
    newdataptr->backtrace = newclock;
    if (newclock) newclock->refcnt++;
    newdataptr->delay = 0.0;
    newdataptr->skew = 0.0;
    newdataptr->setup = 0.0;
    newdataptr->trans = 0.0;
    newdataptr->next = *clocklist;
    *clocklist = newdataptr;
    
    return result;
}

/*--------------------------------------------------------------*/
/* Determine the delay to a clock pin from the farthest point   */
/* back in the network, either to an input pin or the output of */
/* another flop (e.g., a ripple counter).  If there is more     */
/* than one such source (which can happen with, say, a gated    */
/* clock, because this routine will not differentiate between   */
/* the clock signal and the gating signal), then all sources    */
/* recorded (it is only necessary to find a common root of all  */
/* other related clocks downstream).                            */
/*                                                              */
/* This is a recursive routine, continuing to find all delays   */
/* through the circuit until it reaches "terminal".  The        */
/* "delaylist" linked list is not modified by this routine.     */
/* However, it depends on routine find_clock_source marking	*/
/* each connection as visited, so that the search is restricted	*/
/* to the space visited on the upstream search, so it is not	*/
/* blindly recursive.						*/
/*--------------------------------------------------------------*/

void find_clock_delay(int dir, double delay, double trans,
                btptr backtrace, connptr terminal, char minmax,
		unsigned char mode) {

    pinptr  testpin;
    netptr  loadnet;
    cellptr testcell;
    connptr receiver;
    connptr nextrcvr;
    instptr testinst;
    btptr   testbtdata, newbtdata;
    double  newdelayr, newdelayf, newtransr, newtransf;
    short   outdir;
    int     i;

    receiver = backtrace->receiver;

    if (minmax == MAXIMUM_TIME) {
        /* Is delay greater than that already recorded?  If so, replace it */
        if ((delay > backtrace->delay) || (backtrace->delay == INITVAL)) {
            backtrace->delay = delay;
            backtrace->trans = trans;
            backtrace->dir = dir;
        }
    }
    else {
        /* Is delay less than that already recorded?  If so, replace it */
        if ((delay < backtrace->delay) || (backtrace->delay == INITVAL)) {
            backtrace->delay = delay;
            backtrace->trans = trans;
            backtrace->dir = dir;
        }
    }

    // Stop when receiver matches terminal.

    if (receiver != terminal) {

        // Don't follow signal through any DFF pins
	testpin = receiver->refpin;
        testinst = receiver->refinst;
        testcell = (testpin) ? testpin->refcell : NULL;
        if (testcell && (testcell->type & DFF)) return;

        // Compute delay from gate input to output

        outdir = calc_dir(testpin, dir);
        if (outdir & RISING) {
            newdelayr = backtrace->delay +
			calc_prop_delay(trans, receiver, RISING, minmax);
            newtransr = calc_transition(trans, receiver, RISING, minmax);
        }
        if (outdir & FALLING) {
            newdelayf = backtrace->delay +
			calc_prop_delay(trans, receiver, FALLING, minmax);
            newtransf = calc_transition(trans, receiver, FALLING, minmax);
        }

        /* Continue calculating down the backtrace */

	if (outdir & RISING)
	    find_clock_delay(RISING, newdelayr, newtransr, backtrace->next,
			terminal, minmax, mode);
	if (outdir & FALLING)
	    find_clock_delay(FALLING, newdelayf, newtransf, backtrace->next,
			terminal, minmax, mode);
    }
}

/*--------------------------------------------------------------*/
/* Determine the delay from input to output through a gate      */
/*                                                              */
/* This is a recursive routine, continuing to find all delays   */
/* through the circuit until it reaches a terminal or flop      */
/* input.  It is similar to find_clock_delay, but stops on all  */
/* terminal points found in the path, rather than stopping on   */
/* a specific connection.                                       */
/*                                                              */
/* Also unlike find_clock_delay, the routine keeps a running    */
/* record of the path followed from the source, as a character  */
/* string.  When a terminal is found, the path and delay are    */
/* saved and added to "delaylist".  After the recursive search, */
/* "delaylist" contains a list of all paths starting from the   */
/* original connection "receiver" and ending on a clock or an   */
/* output pin.  Where multiple paths exist between source and   */
/* destination, only the path with the longest delay is kept.   */
/*                                                              */
/* Return the number of new paths recorded.                     */
/*--------------------------------------------------------------*/

int find_path_delay(int dir, double delay, double trans, connptr receiver,
                btptr backtrace, ddataptr *delaylist, char minmax) {

    pinptr   testpin;
    netptr   loadnet;
    cellptr  testcell;
    instptr  testinst;
    btptr    newbtdata, freebt, testbt;
    ddataptr testddata, newddata;
    double   newdelayr, newdelayf, newtransr, newtransf;
    short    outdir;
    char     replace;
    int      i, numpaths;

    numpaths = 0;
    testpin = receiver->refpin;

    // Prevent exhaustive search by stopping on a metric.  Note that the
    // nonlinear table-based delay data requires an exhaustive search;
    // generally, the tables can be assumed to be monotonic, in which case
    // we can stop if the delay is less than the greatest delay recorded
    // at this point AND the transition time is less than the transition
    // time recorded along with that delay.  A more relaxed metric is to
    // use the delay plus the transition time, and an even more relaxed
    // metric is to use only the delay.  Any relaxing of the metric
    // implies that the final result may not be the absolute maximum delay,
    // although it will typically vary by less than an average gate delay.

    if (!exhaustive) {
        if (minmax == MAXIMUM_TIME) {
            if (delay <= receiver->metric)
                return numpaths;
        }
        else {
            if (delay >= receiver->metric)
                return numpaths;
        }
    }

    // Check for a logic loop, and truncate the path to avoid infinite
    // looping in the path search.

    if (receiver->tag == (ddataptr)(-1)) return numpaths;
    else if (receiver->tag == NULL) receiver->tag = (ddataptr)(-1);

    // Record this position and delay/transition information

    newbtdata = (btptr)malloc(sizeof(btdata));
    newbtdata->receiver = receiver;
    newbtdata->delay = delay + newbtdata->receiver->icDelay;
    newbtdata->trans = trans;
    newbtdata->dir = dir;
    newbtdata->refcnt = 0;
    newbtdata->next = backtrace;
    if (backtrace) backtrace->refcnt++;

    // Stop when we hit a module output pin or any flop/latch input.
    // We must allow the routine to pass through the 1st register clock (on the first
    // time through, backtrace is NULL).

    if ((backtrace == NULL) || (testpin && ((testpin->type & REGISTER_IN) == 0))) {

        testinst = receiver->refinst;
        testcell = (testpin) ? testpin->refcell : NULL;

        // Compute delay from gate input to output

        outdir = calc_dir(testpin, dir);
        if (outdir & RISING) {
            newdelayr = delay + calc_prop_delay(trans, receiver, RISING, minmax);
            newtransr = calc_transition(trans, receiver, RISING, minmax);
        }
        if (outdir & FALLING) {
            newdelayf = delay + calc_prop_delay(trans, receiver, FALLING, minmax);
            newtransf = calc_transition(trans, receiver, FALLING, minmax);
        }

	if (!testinst || testinst->out_connects) {
	    loadnet = (testinst) ?  testinst->out_connects->refnet : receiver->refnet;
	    for (i = 0; i < loadnet->fanout; i++) {
		if (outdir & RISING)
		    numpaths += find_path_delay(RISING, newdelayr, newtransr,
				loadnet->receivers[i], newbtdata, delaylist, minmax);
		if (outdir & FALLING)
		    numpaths += find_path_delay(FALLING, newdelayf, newtransf,
				loadnet->receivers[i], newbtdata, delaylist, minmax);
	    }
	}
        receiver->tag = NULL;
    }
    else {

        /* Is receiver already in delaylist? */
        if ((receiver->tag != (ddataptr)(-1)) && (receiver->tag != NULL)) {

            /* Position in delaylist is recorded in tag field */
            testddata = receiver->tag;

            if (testddata->backtrace->receiver == receiver) {
                replace = 0;
                if (minmax == MAXIMUM_TIME) {
                    /* Is delay greater than that already recorded?  If so, replace it */
                    if (delay > testddata->backtrace->delay)
                        replace = 1;
                }
                else {
                    /* Is delay less than that already recorded?  If so, replace it */
                    if (delay < testddata->backtrace->delay)
                        replace = 1;
                }
                if (replace) {

                    /* Remove the existing path record and replace it */
                    while (testddata->backtrace != NULL) {
                        freebt = testddata->backtrace;
                        testddata->backtrace = testddata->backtrace->next;
                        freebt->refcnt--;
                        if (freebt->refcnt == 0) free(freebt);
			else break;
                    }
                    testddata->backtrace = newbtdata;
		    if (newbtdata) newbtdata->refcnt++;

		    /* Update the delay at testddata */
		    testddata->delay = newbtdata->delay + testddata->setup
				+ testddata->skew;
                }
            }
            else
                fprintf(stderr, "ERROR:  Bad endpoint tag!\n");
        }
        else
            testddata = NULL;

        // If we have found a propagation path from source to dest,
        // record it in delaylist.

        if (testddata == NULL) {
            numpaths++;
            newddata = (ddataptr)malloc(sizeof(delaydata));
            newddata->delay = 0.0;
            newddata->setup = 0.0;
            newddata->skew = 0.0;
            newddata->trans = 0.0;
            newddata->backtrace = newbtdata;
	    if (newbtdata) newbtdata->refcnt++;
            newddata->next = *delaylist;
            *delaylist = newddata;

            /* Mark the receiver as having been visited */
            receiver->tag = *delaylist;
        }
    }

    receiver->metric = delay;
    if (newbtdata->refcnt <= 0) free(newbtdata);
    return numpaths;
}

/*--------------------------------------------------------------*/
/* Search the list "clocklist" for all points that are module   */
/* inputs or flop outputs, and compute the worst-case           */
/* transition time downstream at testlink.                      */
/*                                                              */
/* Upon return, the worst-case transition time is held in	*/
/* returned backtrace pointer.					*/
/*								*/
/* If mode is non-zero, then only trace paths marked visited ==	*/
/* mode.  This restricts the computation to nodes that were	*/
/* found on the upstream search find_clock_source().		*/
/*--------------------------------------------------------------*/

void find_clock_transition(ddataptr clocklist, connptr testlink, short dir,
	char minmax, unsigned char mode)
{
    ddataptr testclock;
    btptr  backtrace;
    double tdriver, ddelay;

    ddelay = 0.0;
    for (testclock = clocklist; testclock; testclock = testclock->next) {
        backtrace = testclock->backtrace;
        tdriver = 0.0;          // to-do:  set to default input transition time
        find_clock_delay(RISING, ddelay, tdriver, backtrace, testlink,
                        minmax, mode);
        find_clock_delay(FALLING, ddelay, tdriver, backtrace, testlink,
                        minmax, mode);

        // In mode 2, only check timing from 1st item in the list 
	if (mode == (unsigned char)2) break;
    }
}

/*--------------------------------------------------------------*/
/* Find the common point between two clock lists (it has	*/
/* already been determined that a common point exists).		*/
/* The 1st record of clock2list points to the connection	*/
/* record that is the common point.  What is needed is to find	*/
/* the backtrace record in clocklist that points to the same	*/
/* connection record.						*/
/*--------------------------------------------------------------*/

btptr
find_common_clock(ddataptr clock2list, ddataptr clocklist)
{
    ddataptr ddsearch;
    btptr btsearch;
    connptr ccommon;

    ccommon = clock2list->backtrace->receiver;

    for (ddsearch = clocklist; ddsearch; ddsearch = ddsearch->next)
	for (btsearch = ddsearch->backtrace; btsearch; btsearch = btsearch->next)
	    if (btsearch->receiver == ccommon)
		return btsearch;

    return NULL;
}

/*--------------------------------------------------------------*/
/* Given an instance record, find the pin of the instance that  */
/* is the clock, if the instance is a flop.  If the instance is */
/* not a flop, return NULL.                                     */
/*--------------------------------------------------------------*/

connptr find_register_clock(instptr testinst)
{
    connptr testconn;

    for (testconn = testinst->in_connects; testconn; testconn = testconn->next)
        if (testconn->refpin && (testconn->refpin->type & DFFCLK))
            return testconn;

    return NULL;
}

/*--------------------------------------------------------------*/
/* Given an edge direction (RISING or FALLING) at a source net, */
/* and given a destination net, find the sense of the signal    */
/* when it arrives at the destination net.                      */
/*--------------------------------------------------------------*/

short find_edge_dir(short dir, netptr sourcenet, netptr destnet) {
    int i;
    short outdir, rdir;
    connptr testconn, nextconn;
    instptr testinst;
    netptr nextnet;

    for (i = 0; i < sourcenet->fanout; i++) {
        testconn = sourcenet->receivers[i];
        testinst = testconn->refinst;
        if (testinst == NULL) continue;
        if (testconn->refpin == NULL) continue;
        if ((testconn->refpin->type & REGISTER_IN) != 0) continue;
        nextconn = testinst->out_connects;
        nextnet  = nextconn->refnet;
        outdir = calc_dir(testconn->refpin, dir);
        if (nextnet == destnet) return outdir;

        rdir = find_edge_dir(outdir, nextnet, destnet);
        if (rdir != 0) return rdir;
    }
    return 0;
}

/*--------------------------------------------------------------*/
/* Reset all entries in the whole network.  Use sparingly.	*/
/*--------------------------------------------------------------*/

void
reset_all(netptr netlist, char minmax)
{
    netptr testnet;
    connptr testconn;
    int i;
    double metric;

    metric = (minmax == MAXIMUM_TIME) ? -1.0 : 1.0E50;

    for (testnet = netlist; testnet; testnet = testnet->next) {
	for (i = 0; i < testnet->fanout; i++) {
	    testconn = testnet->receivers[i];
	    testconn->tag = NULL;
	    testconn->metric = metric;
	}
    }
}

/*--------------------------------------------------------------*/
/* Reset all entries in the feed-forward tree from connection	*/
/* testconn (recursively).  Resets the tag entry, "visited"	*/
/* flag, and stop metric.					*/
/*--------------------------------------------------------------*/

void
reset_path(connptr testconn, double metric)
{
    int i;
    instptr testinst;
    netptr loadnet;
    connptr nextconn;

    testconn->tag = NULL;
    testconn->visited = (unsigned char)0;
    testconn->metric = metric;

    testinst = testconn->refinst;
    loadnet = (testinst) ? testinst->out_connects->refnet : testconn->refnet;

    for (i = 0; i < loadnet->fanout; i++) {
	nextconn = loadnet->receivers[i];
	if (nextconn->tag != NULL)
	    reset_path(loadnet->receivers[i], metric);
    }
}

/*--------------------------------------------------------------*/
/* Search all paths from the clocked data outputs of            */
/* "clockedlist" to either output pins or data inputs of other  */
/* flops.                                                       */
/*                                                              */
/* Return a master list of all backtraces in "masterlist".      */
/*                                                              */
/* Return value is the number of paths recorded in masterlist.  */
/*                                                              */
/* If minmax == MAXIMUM_TIME, return the maximum delay.         */
/* If minmax == MINIMUM_TIME, return the minimum delay.         */
/*--------------------------------------------------------------*/

int find_clock_to_term_paths(connlistptr clockedlist, ddataptr *masterlist, netptr netlist,
                char minmax)
{
    netptr      testnet;
    connptr     testconn, thisconn;
    connlistptr testlink;
    pinptr      testpin;
    cellptr     testcell;
    instptr     testinst;
    btptr       backtrace, freebt;
    ddataptr    delaylist, testddata, freeddata;

    short       srcdir, destdir;             // Signal direction in/out
    double      tdriver, setupdelay, holddelay, ddelay;
    int         numpaths, n, i, t, j;
    unsigned char result;

    delaylist = NULL;

    t = j = 0;
    if (verbose > 0) {
        for (testlink = clockedlist; testlink; testlink = testlink->next) 
	    t++;
	fprintf(stdout, "Length of list of clocked nets = %d\n", t); 	
	fflush(stdout);
    }

    numpaths = 0;
    for (testlink = clockedlist; testlink; testlink = testlink->next) {
	if (verbose > 0) {
	    j++;
	    if ((j % 100) == 0) {
		fprintf(stdout, "Completed %d traces (%3.1f%%).\n",
			j, 100.0 * ((float)j / (float)t));
		fflush(stdout);
	    }
	}

        // Remove all tags and reset delay metrics in testlink tree
	// before each run

	reset_path(testlink->connection, (minmax == MAXIMUM_TIME) ? -1.0 : 1E50);

        thisconn = testlink->connection;
        testpin = thisconn->refpin;
        if (testpin) {
            testcell = testpin->refcell;

            // Sense is positive for rising edge-triggered flops, negative for
            // falling edge-triggered flops
            srcdir = (testcell->type & CLK_SENSE_MASK) ? FALLING : RISING;

            // Report on paths and their maximum delays
            if (verbose > 1)
                fprintf(stdout, "Paths starting at flop \"%s\" clock:\n\n",
                                thisconn->refinst->name);
        }
        else {
            // Connection is an input pin;  must calculate both rising and falling edges.
            srcdir = EITHER;

            // Report on paths and their maximum delays
            if (verbose > 1)
                fprintf(stdout, "Paths starting at input pin \"%s\"\n\n",
                                thisconn->refnet->name);
        }

        if (verbose > 1) fflush(stdout);

        // Find all paths from "thisconn" to output or a flop input, and compute delay
        n = find_path_delay(srcdir, 0.0, 0.0, thisconn, NULL, &delaylist, minmax);
        numpaths += n;

        if (verbose > 1) fprintf(stdout, "%d paths traced (%d total).\n\n", n, numpaths);

        // Link delaylist data to the beginning of masterlist, and null out
        // delaylist for the next set of paths.

        if (delaylist) {
            for (testddata = delaylist; testddata->next; testddata = testddata->next);
            testddata->next = *masterlist;
            *masterlist = delaylist;
            delaylist = NULL;
        }

    }
    return numpaths;
}

/*--------------------------------------------------------------*/
/* Parse a table variable type from a liberty format file       */
/*--------------------------------------------------------------*/

int get_table_type(char *token) {
    if (!strcasecmp(token, "input_net_transition"))
        return TRANSITION_TIME;
    else if (!strcasecmp(token, "total_output_net_capacitance"))
        return OUTPUT_CAP;
    else if (!strcasecmp(token, "related_pin_transition"))
        return RELATED_TIME;
    else if (!strcasecmp(token, "constrained_pin_transition"))
        return CONSTRAINED_TIME;
    else
        return UNKNOWN;
}

/*--------------------------------------------------------------*/
/* Expand a bus into individual pins.				*/
/*--------------------------------------------------------------*/

void
expand_buses(pin *curpin, bus *curbus, char *busformat)
{
    pin *newpin;
    char *rootname;
    int low, high, i;
    char busbit[1024];

    rootname = curpin->name;

    if (curbus == NULL) {
	fprintf(stderr, "Error:  Pin %s is a bus, but no bus definition exists!\n",
			curpin->name);
	return;
    }
    if (curbus->from > curbus->to) {
	low = curbus->to;
	high = curbus->from;
    }
    else {
	low = curbus->from;
	high = curbus->to;
    }

    for (i = low; i <= high; i++) {
	sprintf(busbit, busformat, rootname, i);

	if (i == low)
	    newpin = curpin;
	else
	    newpin = (pin *)malloc(sizeof(pin));
	newpin->name = strdup(busbit);
	newpin->next = NULL;
	if (i != low) {
	    curpin->next = newpin;

	    /* Copy all pin properties from the old pin to the new pin */
	    newpin->type = curpin->type;
	    newpin->capr = curpin->capr;
	    newpin->capf = curpin->capf;
	    newpin->sense = curpin->sense;
	    newpin->transr = curpin->transr;
	    newpin->transf = curpin->transf;
	    newpin->propdelr = curpin->propdelr;
	    newpin->propdelf = curpin->propdelf;
	    newpin->refcell = curpin->refcell;

	    curpin = newpin;
	}
    }
    free(rootname);
}

/*--------------------------------------------------------------*/
/* Read a liberty format file and collect information about     */
/* the timing properties of each standard cell.                 */
/*--------------------------------------------------------------*/

void
libertyRead(FILE *flib, lutable **tablelist, cell **celllist)
{
    char *token;
    char *libname = NULL;
    int section = INIT;

    double time_unit = 1.0;     // Time unit multiplier, to get ps
    double cap_unit = 1.0;      // Capacitive unit multiplier, to get fF

    pinptr testpin;
    lutable *tableptr;

    int i, j;
    double gval;
    char *iptr;
    char *busformat;
    short timing_type, sense_type;

    lutable *newtable, *reftable;
    cell *newcell, *lastcell;
    pin *newpin;
    bus *buses = NULL, *newbus, *curbus = NULL;

    lastcell = NULL;
    timing_type = UNKNOWN;

    /* Set default bus format (verilog style) */
    busformat = strdup("%s[%d]");

    /* Read tokens off of the line */
    token = advancetoken(flib, 0);

    while (token != NULL) {

        switch (section) {
            case INIT:
		if (debug == 2) fprintf(stdout, "INIT: %s\n", token);
                if (!strcasecmp(token, "library")) {
                    token = advancetoken(flib, 0);
                    if (strcmp(token, "("))
                        fprintf(stderr, "Library not followed by name\n");
                    else
                        token = advancetoken(flib, ')');
		    /* Diagnostic */
                    fprintf(stdout, "Parsing library \"%s\"\n", token);
                    libname = strdup(token);
                    token = advancetoken(flib, 0);
                    if (strcmp(token, "{")) {
                        fprintf(stderr, "Did not find opening brace "
                                        "on library block\n");
                        exit(1);
                    }
                    section = LIBBLOCK;
                }
                else
                    fprintf(stderr, "Unknown input \"%s\", looking for "
                                        "\"library\"\n", token);
                break;

            case LIBBLOCK:
                // Here we check for the main blocks, again not rigorously. . .

		if (debug == 2) fprintf(stdout, "LIBBLOCK: %s\n", token);
                if (!strcasecmp(token, "}")) {
                    fprintf(stdout, "End of library at line %d\n", fileCurrentLine);
                    section = INIT;                     // End of library block
                }
                else if (!strcasecmp(token, "delay_model")) {
                    token = advancetoken(flib, 0);
                    if (strcmp(token, ":"))
                        fprintf(stderr, "Input missing colon\n");
                    token = advancetoken(flib, ';');
                    if (strcasecmp(token, "table_lookup")) {
                        fprintf(stderr, "Sorry, only know how to "
                                        "handle table lookup!\n");
                        exit(1);
                    }
                }
                else if (!strcasecmp(token, "lu_table_template") ||
                         !strcasecmp(token, "power_lut_template")) {
                    // Read in template information;
                    newtable = (lutable *)malloc(sizeof(lutable));
                    newtable->name = NULL;
                    newtable->invert = 0;
                    newtable->var1 = UNKNOWN;
                    newtable->var2 = UNKNOWN;
                    newtable->size1 = 0;
                    newtable->size2 = 0;
                    newtable->idx1.times = NULL;
                    newtable->idx2.caps = NULL;
                    newtable->values = NULL;
                    newtable->next = *tablelist;
                    *tablelist = newtable;

                    token = advancetoken(flib, 0);
                    if (strcmp(token, "("))
                        fprintf(stderr, "Input missing open parens\n");
                    else
                        token = advancetoken(flib, ')');
                    newtable->name = strdup(token);
                    while (*token != '}') {
                        token = advancetoken(flib, 0);
                        if (!strcasecmp(token, "variable_1")) {
                            token = advancetoken(flib, 0);
                            token = advancetoken(flib, ';');
                            newtable->var1 = get_table_type(token);
                            if (newtable->var1 == OUTPUT_CAP || newtable->var1 == CONSTRAINED_TIME)
                                newtable->invert = 1;
                        }
                        else if (!strcasecmp(token, "variable_2")) {
                            token = advancetoken(flib, 0);
                            token = advancetoken(flib, ';');
                            newtable->var2 = get_table_type(token);
                            if (newtable->var2 == TRANSITION_TIME || newtable->var2 == RELATED_TIME)
                                newtable->invert = 1;
                        }
                        else if (!strcasecmp(token, "index_1")) {
			    char dnum = ',';

                            token = advancetoken(flib, 0);      // Open parens
                            token = advancetoken(flib, 0);      // Quote
                            if (!strcmp(token, "\""))
                                token = advancetoken(flib, '\"');

                            iptr = token;

			    // Check if table is space or comma separated
			    if (strchr(iptr, dnum) == NULL)
				if (strchr(iptr, ' ') != NULL)
				    dnum = ' ';

                            if (newtable->invert == 1) {
                                // Count entries
                                newtable->size2 = 1;
                                while ((iptr = strchr(iptr, dnum)) != NULL) {
                                    iptr++;
                                    newtable->size2++;
                                }
                                newtable->idx2.caps = (double *)malloc(newtable->size2 *
                                        sizeof(double));
                                newtable->size2 = 0;
                                iptr = token;
                                sscanf(iptr, "%lg", &newtable->idx2.caps[0]);
                                if (newtable->var2 == OUTPUT_CAP)
                                    newtable->idx2.caps[0] *= cap_unit;
                                else
                                    newtable->idx2.caps[0] *= time_unit;

                                while ((iptr = strchr(iptr, dnum)) != NULL) {
                                    iptr++;
                                    newtable->size2++;
                                    sscanf(iptr, "%lg",
                                                &newtable->idx2.caps[newtable->size2]);
                                    if (newtable->var2 == OUTPUT_CAP)
                                        newtable->idx2.caps[newtable->size2] *= cap_unit;
                                    else
                                        newtable->idx2.cons[newtable->size2] *= time_unit;
                                }
                                newtable->size2++;
                            }
                            else {      // newtable->invert = 0
                                // Count entries
                                newtable->size1 = 1;
                                while ((iptr = strchr(iptr, dnum)) != NULL) {
                                    iptr++;
                                    newtable->size1++;
                                }
                                newtable->idx1.times = (double *)malloc(newtable->size1 *
                                        sizeof(double));
                                newtable->size1 = 0;
                                iptr = token;
                                sscanf(iptr, "%lg", &newtable->idx1.times[0]);
                                newtable->idx1.times[0] *= time_unit;
                                while ((iptr = strchr(iptr, dnum)) != NULL) {
                                    iptr++;
                                    newtable->size1++;
                                    sscanf(iptr, "%lg",
                                                &newtable->idx1.times[newtable->size1]);
                                    newtable->idx1.times[newtable->size1] *= time_unit;
                                }
                                newtable->size1++;
                            }

                            token = advancetoken(flib, ';'); // EOL semicolon
                        }
                        else if (!strcasecmp(token, "index_2")) {
			    char dnum = ',';

                            token = advancetoken(flib, 0);      // Open parens
                            token = advancetoken(flib, 0);      // Quote
                            if (!strcmp(token, "\""))
                                token = advancetoken(flib, '\"');

                            iptr = token;

			    // Check if table is space or comma separated
			    if (strchr(iptr, dnum) == NULL)
				if (strchr(iptr, ' ') != NULL)
				    dnum = ' ';

                            if (newtable->invert == 0) {
                                // Count entries
                                newtable->size2 = 1;
                                while ((iptr = strchr(iptr, dnum)) != NULL) {
                                    iptr++;
                                    newtable->size2++;
                                }
                                newtable->idx2.caps = (double *)malloc(newtable->size2 *
                                        sizeof(double));
                                newtable->size2 = 0;
                                iptr = token;
                                sscanf(iptr, "%lg", &newtable->idx2.caps[0]);
                                if (newtable->var2 == OUTPUT_CAP)
                                    newtable->idx2.caps[0] *= cap_unit;
                                else
                                    newtable->idx2.cons[0] *= time_unit;
                                while ((iptr = strchr(iptr, dnum)) != NULL) {
                                    iptr++;
                                    newtable->size2++;
                                    sscanf(iptr, "%lg",
                                                &newtable->idx2.caps[newtable->size2]);
                                    if (newtable->var2 == OUTPUT_CAP)
                                        newtable->idx2.caps[newtable->size2] *= cap_unit;
                                    else
                                        newtable->idx2.cons[newtable->size2] *= time_unit;
                                }
                                newtable->size2++;
                            }
                            else {      // newtable->invert == 1
                                // Count entries
                                newtable->size1 = 1;
                                while ((iptr = strchr(iptr, dnum)) != NULL) {
                                    iptr++;
                                    newtable->size1++;
                                }
                                newtable->idx1.times = (double *)malloc(newtable->size1 *
                                        sizeof(double));
                                newtable->size1 = 0;
                                iptr = token;
                                sscanf(iptr, "%lg", &newtable->idx1.times[0]);
                                newtable->idx1.times[0] *= time_unit;
                                while ((iptr = strchr(iptr, dnum)) != NULL) {
                                    iptr++;
                                    newtable->size1++;
                                    sscanf(iptr, "%lg",
                                                &newtable->idx1.times[newtable->size1]);
                                    newtable->idx1.times[newtable->size1] *= time_unit;
                                }
                                newtable->size1++;
                            }

                            token = advancetoken(flib, ';'); // EOL semicolon
                        }
                    }
                }
                else if (!strcasecmp(token, "cell")) {
                    newcell = (cell *)malloc(sizeof(cell));
                    newcell->next = NULL;
                    if (lastcell != NULL)
                        lastcell->next = newcell;
                    else
                        *celllist = newcell;
                    lastcell = newcell;
                    token = advancetoken(flib, 0);      // Open parens
                    if (!strcmp(token, "("))
                        token = advancetoken(flib, ')');        // Cellname
		    if (debug == 2) fprintf(stdout, "   cell = %s\n", token);
                    newcell->name = strdup(token);
                    token = advancetoken(flib, 0);      // Find start of block
                    if (strcmp(token, "{"))
                        fprintf(stderr, "Error: failed to find start of block\n");
                    newcell->type = GATE;               // Default type
                    newcell->function = NULL;
                    newcell->pins = NULL;
                    newcell->area = 1.0;
                    newcell->maxtrans = 0.0;
                    newcell->maxcap = 0.0;
                    section = CELLDEF;
                }
                else if (!strcasecmp(token, "time_unit")) {
                   char *metric;

                   token = advancetoken(flib, 0);
                   if (token == NULL) break;
                   if (!strcmp(token, ":")) {
                      token = advancetoken(flib, 0);
                      if (token == NULL) break;
                   }
                   if (!strcmp(token, "\"")) {
                      token = advancetoken(flib, '\"');
                      if (token == NULL) break;
                   }
                   time_unit = strtod(token, &metric);
                   if (*metric != '\0') {
                      if (!strcmp(metric, "ns"))
                         time_unit *= 1E3;
                      else if (!strcmp(metric, "us"))
                         time_unit *= 1E6;
                      else if (!strcmp(metric, "fs"))
                         time_unit *= 1E-3;
                      else if (strcmp(metric, "ps"))
                         fprintf(stderr, "Don't understand time units \"%s\"\n",
                                token);
                   }
                   else {
                      token = advancetoken(flib, 0);
                      if (token == NULL) break;
                      if (!strcmp(token, "ns"))
                         time_unit *= 1E3;
                      else if (!strcmp(token, "us"))
                         time_unit *= 1E6;
                      else if (!strcmp(token, "fs"))
                         time_unit *= 1E-3;
                      else if (strcmp(token, "ps"))
                         fprintf(stderr, "Don't understand time units \"%s\"\n",
                                token);
                   }
                   token = advancetoken(flib, ';');
                }
                else if (!strcasecmp(token, "capacitive_load_unit")) {
                   char *metric;

                   token = advancetoken(flib, 0);
                   if (token == NULL) break;
                   if (!strcmp(token, "(")) {
                      token = advancetoken(flib, ')');
                      if (token == NULL) break;
                   }
                   cap_unit = strtod(token, &metric);
                   if (*metric != '\0') {
                      while (isblank(*metric)) metric++;
                      if (*metric == ',') metric++;
                      while ((*metric != '\0') && isblank(*metric)) metric++;
		      if (*metric == '\"') {
			 char *qptr;
			 metric++;
			 qptr = metric;
			 while ((*qptr != '\"') && (*qptr != '\0')) qptr++;
			 *qptr = '\0';
		      }
                      if (!strcasecmp(metric, "af"))
                         cap_unit *= 1E-3;
                      else if (!strcasecmp(metric, "pf"))
                         cap_unit *= 1000;
                      else if (!strcasecmp(metric, "nf"))
                         cap_unit *= 1E6;
                      else if (!strcasecmp(metric, "uf"))
                         cap_unit *= 1E9;
                      else if (strcasecmp(metric, "ff"))
                         fprintf(stderr, "Don't understand capacitive units \"%s\"\n",
                                token);
                   }
                   else {
                      token = advancetoken(flib, 0);
                      if (token == NULL) break;
                      if (!strcasecmp(token, "af"))
                         cap_unit *= 1E-3;
                      else if (!strcasecmp(token, "pf"))
                         cap_unit *= 1000;
                      else if (!strcasecmp(token, "nf"))
                         cap_unit *= 1E6;
                      else if (!strcasecmp(token, "uf"))
                         cap_unit *= 1E9;
                      else if (strcasecmp(token, "ff"))
                         fprintf(stderr, "Don't understand capacitive units \"%s\"\n",
                                token);
                   }
                   token = advancetoken(flib, ';');
                }
		else if (!strcasecmp(token, "bus_naming_style")) {
		    token = advancetoken(flib, 0);
		    if (token == NULL) break;
		    if (!strcmp(token, ":")) {
			token = advancetoken(flib, 0);
			if (token == NULL) break;
		    }
		    if (!strcmp(token, "\"")) {
			token = advancetoken(flib, '\"');
			if (token == NULL) break;
		    }
		    free(busformat);
		    busformat = strdup(token);
                    token = advancetoken(flib, ';');
		}
		else if (!strcasecmp(token, "type")) {
		    newbus = (bus *)malloc(sizeof(bus));
		    newbus->from = 0;
		    newbus->to = 0;
		    newbus->next = buses;
		    buses = newbus;

		    token = advancetoken(flib, 0);
		    if (strcmp(token, "("))
			fprintf(stderr, "Input missing open parenthesis.\n");
		    else
			token = advancetoken(flib, ')');
		    newbus->name = strdup(token);
		    while (*token != '}') {
			token = advancetoken(flib, 0);
			if (!strcasecmp(token, "bit_from")) {
			    token = advancetoken(flib, 0);
			    token = advancetoken(flib, ';');
			    sscanf(token, "%d", &newbus->from);
			}
			else if (!strcasecmp(token, "bit_to")) {
			    token = advancetoken(flib, 0);
			    token = advancetoken(flib, ';');
			    sscanf(token, "%d", &newbus->to);
			}
			else if (!strcasecmp(token, "{")) {
			    /* All entries are <name> : <value> */
			    /* Ignore unhandled tokens.		*/
			    token = advancetoken(flib, 0);
			    token = advancetoken(flib, ';');
			}
		    }
		}
                else {
                    // For unhandled tokens, read in tokens.  If it is
                    // a definition or function, read to end-of-line.  If
                    // it is a block definition, read to end-of-block.
                    while (1) {
                        token = advancetoken(flib, 0);
                        if (token == NULL) break;
                        if (!strcmp(token, ";")) break;
                        if (!strcmp(token, "\""))
                            token = advancetoken(flib, '\"');
                        if (!strcmp(token, "{")) {
                            token = advancetoken(flib, '}');
                            break;
                        }
                    }
                }
                break;

            case CELLDEF:

		if (debug == 2) fprintf(stdout, "CELLDEF: %s\n", token);
                if (!strcmp(token, "}")) {
                    section = LIBBLOCK;                 // End of cell def
                }
                else if (!strcasecmp(token, "pin") ||
			 !strcasecmp(token, "bus")) {
                    token = advancetoken(flib, 0);      // Open parens
                    if (!strcmp(token, "("))
                        token = advancetoken(flib, ')');        // Close parens

                    newpin = parse_pin(newcell, token, SENSE_NONE);
		    if (debug == 2) fprintf(stdout, "   pin = %s\n", token);

                    token = advancetoken(flib, 0);      // Find start of block
                    if (strcmp(token, "{"))
                        fprintf(stderr, "Error: failed to find start of block\n");
                    section = PINDEF;
                }
                else if (!strcasecmp(token, "area")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    sscanf(token, "%lg", &newcell->area);
                }
                else if (!strcasecmp(token, "ff")) {
                    newcell->type |= DFF;
                    token = advancetoken(flib, '{');
                    section = FLOPDEF;
                }
                else if (!strcasecmp(token, "latch")) {
                    newcell->type |= LATCH;
                    token = advancetoken(flib, '{');
                    section = LATCHDEF;
                }
                else {
                    // For unhandled tokens, read in tokens.  If it is
                    // a definition or function, read to end-of-line.  If
                    // it is a block definition, read to end-of-block.
                    while (1) {
                        token = advancetoken(flib, 0);
                        if (token == NULL) break;
                        if (!strcmp(token, ";")) break;
                        if (!strcmp(token, "\""))
                            token = advancetoken(flib, '\"');
                        if (!strcmp(token, "("))
                            token = advancetoken(flib, ')');
                        if (!strcmp(token, "{")) {
                            token = advancetoken(flib, '}');
                            break;
                        }
                    }
                }
                break;

            case FLOPDEF:

		if (debug == 2) fprintf(stdout, "FLOPDEF: %s\n", token);
                if (!strcmp(token, "}")) {
                    section = CELLDEF;                  // End of flop def
                }
                else if (!strcasecmp(token, "next_state")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    newpin = parse_pin(newcell, token, SENSE_NONE);
                    newpin->type |= DFFIN;
                }
                else if (!strcasecmp(token, "clocked_on")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    if (strchr(token, '\'') != NULL)
                        newcell->type |= CLK_SENSE_MASK;
                    else if (strchr(token, '!') != NULL)
                        newcell->type |= CLK_SENSE_MASK;
                    newpin = parse_pin(newcell, token, SENSE_NONE);
                    newpin->type |= DFFCLK;
                }
                else if (!strcasecmp(token, "clear")) {
                    newcell->type |= RST_MASK;
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    if (strchr(token, '\'') != NULL)
                        newcell->type |= RST_SENSE_MASK;
                    else if (strchr(token, '!') != NULL)
                        newcell->type |= RST_SENSE_MASK;
                    newpin = parse_pin(newcell, token, SENSE_NONE);
                    newpin->type |= DFFRST;
                }
                else if (!strcasecmp(token, "preset")) {
                    newcell->type |= SET_MASK;
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    if (strchr(token, '\'') != NULL)
                        newcell->type |= SET_SENSE_MASK;
                    else if (strchr(token, '!') != NULL)
                        newcell->type |= SET_SENSE_MASK;
                    newpin = parse_pin(newcell, token, SENSE_NONE);
                    newpin->type |= DFFSET;
                }
                else
                    token = advancetoken(flib, ';');    // Read to end-of-statement

                break;

            case LATCHDEF:

		if (debug == 2) fprintf(stdout, "LATCHDEF: %s\n", token);
                if (!strcmp(token, "}")) {
                    section = CELLDEF;                  // End of flop def
                }
                else if (!strcasecmp(token, "data_in")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    newpin = parse_pin(newcell, token, SENSE_NONE);
                    newpin->type |= LATCHIN;
                }
                else if (!strcasecmp(token, "enable")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    if (strchr(token, '\'') != NULL)
                        newcell->type |= EN_SENSE_MASK;
                    else if (strchr(token, '!') != NULL)
                        newcell->type |= EN_SENSE_MASK;
                    newpin = parse_pin(newcell, token, SENSE_NONE);
                    newpin->type |= LATCHEN;
                }
                else
                    token = advancetoken(flib, ';');    // Read to end-of-statement

                break;

            case PINDEF:

		if (debug == 2) fprintf(stdout, "PINDEF: %s\n", token);
                if (!strcmp(token, "}")) {
		    if (curbus != NULL)
			expand_buses(newpin, curbus, busformat);
		    curbus = NULL;
                    section = CELLDEF;                  // End of pin def
                }
                else if (!strcasecmp(token, "capacitance")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    sscanf(token, "%lg", &newpin->capr);
                    newpin->capr *= cap_unit;
                }
                else if (!strcasecmp(token, "rise_capacitance")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    sscanf(token, "%lg", &newpin->capr);
                    newpin->capr *= cap_unit;
                }
                else if (!strcasecmp(token, "fall_capacitance")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    sscanf(token, "%lg", &newpin->capf);
                    newpin->capf *= cap_unit;
                }
                else if (!strcasecmp(token, "function")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, 0);      // Open quote
                    if (!strcmp(token, "\""))
                        token = advancetoken(flib, '\"');       // Find function string
                    if (newpin->type & OUTPUT) {
                        newcell->function = strdup(token);
                    }
                    token = advancetoken(flib, 0);
                    if (strcmp(token, ";")) {
                        if (!strcmp(token, "}"))
                            section = CELLDEF;          // End of pin def
                        else
                            fprintf(stderr, "Expected end-of-statement.\n");
                    }
                }
		else if (!strcasecmp(token, "bus_type")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');
		    /* Find the bus definition */
		    for (curbus = buses; curbus; curbus = curbus->next)
			if (!strcmp(curbus->name, token))
			    break;
		    if (curbus == NULL)
			fprintf(stderr, "Failed to find a valid bus type \"%s\"\n",
				token);
		}
                else if (!strcasecmp(token, "direction")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');
                    if (!strcasecmp(token, "input")) {
                        newpin->type |= INPUT;
                    }
                    else if (!strcasecmp(token, "output")) {
                        newpin->type |= OUTPUT;
                        if (newcell->type & DFF) newpin->type |= DFFOUT;
                        if (newcell->type & LATCH) newpin->type |= LATCHOUT;
                    }
                }
                else if (!strcasecmp(token, "max_transition")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    sscanf(token, "%lg", &newcell->maxtrans);
                    newcell->maxtrans *= time_unit;
                }
                else if (!strcasecmp(token, "max_capacitance")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    sscanf(token, "%lg", &newcell->maxcap);
                    newcell->maxcap *= cap_unit;
                }
                else if (!strcasecmp(token, "timing")) {
                    token = advancetoken(flib, 0);      // Arguments, if any
                    if (strcmp(token, "("))
                        fprintf(stderr, "Error: failed to find start of block\n");
                    else
                       token = advancetoken(flib, ')'); // Arguments, if any
                    token = advancetoken(flib, 0);      // Find start of block
                    if (strcmp(token, "{"))
                        fprintf(stderr, "Error: failed to find start of block\n");
                    testpin = NULL;
                    sense_type = SENSE_NONE;
                    section = TIMING;
                }
                else {
                    // For unhandled tokens, read in tokens.  If it is
                    // a definition or function, read to end-of-line.  If
                    // it is a block definition, read to end-of-block.
                    while (1) {
                        token = advancetoken(flib, 0);
                        if (token == NULL) break;
                        if (!strcmp(token, ";")) break;
                        if (!strcmp(token, "\""))
                            token = advancetoken(flib, '\"');
                        if (!strcmp(token, "{")) {
                            token = advancetoken(flib, '}');
                            break;
                        }
                    }
                }
                break;

            case TIMING:

		if (debug == 2) fprintf(stdout, "TIMING: %s\n", token);
                if (!strcmp(token, "}")) {
                    section = PINDEF;                   // End of timing def
                }
                else if (!strcasecmp(token, "related_pin")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // Read to end of statement
                    // Create the related pin if needed
                    testpin = parse_pin(newcell, token, sense_type);
                }
                else if (!strcasecmp(token, "timing_sense")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // Read to end of statement
                    if (!strcasecmp(token, "positive_unate")) {
                        if (testpin)
                           testpin->sense = SENSE_POSITIVE;
                        else
                           sense_type = SENSE_POSITIVE;
                    }
                    else if (!strcasecmp(token, "negative_unate")) {
                        if (testpin)
                           testpin->sense = SENSE_NEGATIVE;
                        else
                           sense_type = SENSE_NEGATIVE;
                    }
                    else if (!strcasecmp(token, "non_unate")) {
                        if (testpin)
                           testpin->sense = SENSE_NONE;
                        else
                           sense_type = SENSE_NONE;
                    }
                }
                else if (!strcasecmp(token, "timing_type")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // Read to end of statement

                    // Note:  Timing type is apparently redundant information;
                    // e.g., "falling_edge" can be determined by "clocked_on : !CLK"
                    // in the ff {} block.  How reliable is this?

                    if (!strcasecmp(token, "rising_edge"))
                        timing_type = TIMING_PROP_TRANS;
                    else if (!strcasecmp(token, "falling_edge"))
                        timing_type = TIMING_PROP_TRANS;
                    else if (!strcasecmp(token, "hold_rising"))
                        timing_type = TIMING_HOLD;
                    else if (!strcasecmp(token, "hold_falling"))
                        timing_type = TIMING_HOLD;
                    else if (!strcasecmp(token, "setup_rising"))
                        timing_type = TIMING_SETUP;
                    else if (!strcasecmp(token, "setup_falling"))
                        timing_type = TIMING_SETUP;
                    else if (!strcasecmp(token, "clear"))
                        timing_type = TIMING_SET_RESET;
                    else if (!strcasecmp(token, "preset"))
                        timing_type = TIMING_SET_RESET;
                    else if (!strcasecmp(token, "recovery_rising"))
                        timing_type = TIMING_RECOVERY;
                    else if (!strcasecmp(token, "recovery_falling"))
                        timing_type = TIMING_RECOVERY;
                    else if (!strcasecmp(token, "removal_rising"))
                        timing_type = TIMING_REMOVAL;
                    else if (!strcasecmp(token, "removal_falling"))
                        timing_type = TIMING_REMOVAL;
                    else if (!strcasecmp(token, "three_state_enable"))
                        timing_type = TIMING_TRISTATE;
                    else if (!strcasecmp(token, "three_state_disable"))
                        timing_type = TIMING_TRISTATE;
                }
                else if ((!strcasecmp(token, "cell_rise")) ||
                        (!strcasecmp(token, "cell_fall")) ||
                        (!strcasecmp(token, "rise_transition")) ||
                        (!strcasecmp(token, "fall_transition")) ||
                        (!strcasecmp(token, "rise_constraint")) ||
                        (!strcasecmp(token, "fall_constraint"))) {

                    tableptr = (lutable *)malloc(sizeof(lutable));
                    tableptr->name = NULL;      // Not used
                    tableptr->invert = 0;
                    tableptr->var1 = UNKNOWN;
                    tableptr->var2 = UNKNOWN;
                    tableptr->size1 = 0;
                    tableptr->size2 = 0;
                    tableptr->idx1.times = NULL;
                    tableptr->idx2.caps = NULL;
                    tableptr->values = NULL;
                    tableptr->next = NULL;      // Not used

                    // Note that propagation delays (cell rise, cell fall) and
                    // transition times (rise transition, fall transition) have
                    // their lookup tables stored in the "related pin" pin record.
                    // Setup and hold times (rise constraint, fall constraint)
                    // have their lookup tables stored in the original pin record.
                    // These should not overlap.

                    // Recovery and removal tables are not yet handled. . .

                    if (!strcasecmp(token, "cell_rise"))
                        testpin->propdelr = tableptr;
                    else if (!strcasecmp(token, "cell_fall"))
                        testpin->propdelf = tableptr;
                    else if (!strcasecmp(token, "rise_transition"))
                        testpin->transr = tableptr;
                    else if (!strcasecmp(token, "fall_transition"))
                        testpin->transf = tableptr;
                    else if (!strcasecmp(token, "rise_constraint")) {
                        if (timing_type == TIMING_SETUP)
                            newpin->propdelr = tableptr;
                        else if (timing_type == TIMING_HOLD)
                            newpin->transr = tableptr;
                    }
                    else if (!strcasecmp(token, "fall_constraint")) {
                        if (timing_type == TIMING_SETUP)
                            newpin->propdelf = tableptr;
                        else if (timing_type == TIMING_HOLD)
                            newpin->transf = tableptr;
                    }

                    token = advancetoken(flib, 0);      // Open parens
                    if (!strcmp(token, "("))
                        token = advancetoken(flib, ')');

                    for (reftable = *tablelist; reftable; reftable = reftable->next)
                        if (!strcmp(reftable->name, token))
                            break;
                    if (reftable == NULL)
                        fprintf(stderr, "Failed to find a valid table \"%s\"\n",
                                token);
                    else {
                        // Fill in default values from template reftable
                        tableptr->invert = reftable->invert;
                        if (reftable->size1 > 0) {
                            tableptr->var1 = reftable->var1;
                            tableptr->size1 = reftable->size1;
                            tableptr->idx1.times = (double *)malloc(tableptr->size1 * sizeof(double));
                            memcpy(tableptr->idx1.times, reftable->idx1.times,
                                                tableptr->size1 * sizeof(double));
                        }
                        if (reftable->size2 > 0) {
                            tableptr->var2 = reftable->var2;
                            tableptr->size2 = reftable->size2;
                            tableptr->idx2.caps = (double *)malloc(tableptr->size2 * sizeof(double));
                            memcpy(tableptr->idx2.caps, reftable->idx2.caps,
                                                tableptr->size2 * sizeof(double));
                        }
                    }

                    token = advancetoken(flib, 0);
                    if (strcmp(token, "{"))
                        fprintf(stderr, "Failed to find start of timing block\n");

                    while (*token != '}') {
                        token = advancetoken(flib, 0);
                        if (!strcasecmp(token, "index_1")) {
			    char dnum = ',';

                            // Local index values override those in the template

                            token = advancetoken(flib, 0);      // Open parens
                            token = advancetoken(flib, 0);      // Quote
                            if (!strcmp(token, "\""))
                                token = advancetoken(flib, '\"');

                            iptr = token;

			    // Check if table is space or comma separated
			    if (strchr(iptr, dnum) == NULL)
				if (strchr(iptr, ' ') != NULL)
				    dnum = ' ';

                            //-------------------------

                            if (reftable && (reftable->invert == 1)) {
                                // Entries had better match the ref table
                                i = 0;
                                sscanf(iptr, "%lg", &tableptr->idx2.caps[0]);
                                if (tableptr->var2 == OUTPUT_CAP)
                                    tableptr->idx2.caps[0] *= cap_unit;
                                else
                                    tableptr->idx2.cons[0] *= time_unit;
                                while ((iptr = strchr(iptr, dnum)) != NULL) {
                                    iptr++;
                                    i++;
                                    sscanf(iptr, "%lg", &tableptr->idx2.caps[i]);
                                    if (tableptr->var2 == OUTPUT_CAP)
                                        tableptr->idx2.caps[i] *= cap_unit;
                                    else
                                        tableptr->idx2.cons[i] *= time_unit;
                                }
                            }
                            else if (reftable && (reftable->invert == 0)) {
                                iptr = token;
                                i = 0;
                                sscanf(iptr, "%lg", &tableptr->idx1.times[0]);
                                tableptr->idx1.times[0] *= time_unit;
                                while ((iptr = strchr(iptr, dnum)) != NULL) {
                                    iptr++;
                                    i++;
                                    sscanf(iptr, "%lg", &tableptr->idx1.times[i]);
                                    tableptr->idx1.times[i] *= time_unit;
                                }
                            }

                            token = advancetoken(flib, ')');    // Close paren
                            token = advancetoken(flib, ';');    // EOL semicolon
                        }
                        else if (!strcasecmp(token, "index_2")) {
			    char dnum = ',';

                            // Local index values override those in the template

                            token = advancetoken(flib, 0);      // Open parens
                            token = advancetoken(flib, 0);      // Quote
                            if (!strcmp(token, "\""))
                                token = advancetoken(flib, '\"');

                            iptr = token;

			    // Check if table is space or comma separated
			    if (strchr(iptr, dnum) == NULL)
				if (strchr(iptr, ' ') != NULL)
				    dnum = ' ';

                            //-------------------------

                            if (reftable && (reftable->invert == 1)) {
                                // Entries had better match the ref table
                                i = 0;
                                sscanf(iptr, "%lg", &tableptr->idx1.times[0]);
                                tableptr->idx1.times[0] *= time_unit;
                                while ((iptr = strchr(iptr, dnum)) != NULL) {
                                    iptr++;
                                    i++;
                                    sscanf(iptr, "%lg", &tableptr->idx1.times[i]);
                                    tableptr->idx1.times[i] *= time_unit;
                                }
                            }
                            else if (reftable && (reftable->invert == 0)) {
                                iptr = token;
                                i = 0;
                                sscanf(iptr, "%lg", &tableptr->idx2.caps[0]);
                                tableptr->idx2.caps[0] *= cap_unit;
                                while ((iptr = strchr(iptr, dnum)) != NULL) {
                                    iptr++;
                                    i++;
                                    sscanf(iptr, "%lg", &tableptr->idx2.caps[i]);
                                    tableptr->idx2.caps[i] *= cap_unit;
                                }
                            }

                            token = advancetoken(flib, ')');    // Close paren
                            token = advancetoken(flib, ';');    // EOL semicolon
                        }
                        else if (!strcasecmp(token, "values")) {
                            token = advancetoken(flib, 0);
                            if (strcmp(token, "("))
                                fprintf(stderr, "Failed to find start of"
                                                " value table\n");
                            token = advancetoken(flib, ')');

                            // Parse the string of values and enter it into the
                            // table "values", which is size size2 x size1

                            if (reftable && reftable->size1 > 0) {
                                int locsize2;
                                locsize2 = (reftable->size2 > 0) ? reftable->size2 : 1;
                                if (reftable->invert) {
                                    tableptr->values = (double *)malloc(locsize2 *
                                                reftable->size1 * sizeof(double));
                                    iptr = token;
                                    for (i = 0; i < reftable->size1; i++) {
                                        for (j = 0; j < locsize2; j++) {
                                            while (*iptr == ' ' || *iptr == '\"' ||
                                                        *iptr == ',' || *iptr == '\\')
                                                iptr++;
                                            sscanf(iptr, "%lg", &gval);
                                            *(tableptr->values + j * reftable->size1
                                                        + i) = gval * time_unit;
                                            while (*iptr != ' ' && *iptr != '\"' &&
                                                        *iptr != ',' || *iptr == '\\')
                                                iptr++;
                                        }
                                    }
                                }
                                else {
                                    tableptr->values = (double *)malloc(locsize2 *
                                                reftable->size1 * sizeof(double));
                                    iptr = token;
                                    for (j = 0; j < locsize2; j++) {
                                        for (i = 0; i < reftable->size1; i++) {
                                            while (*iptr == ' ' || *iptr == '\"' ||
                                                        *iptr == ',' || *iptr == '\\')
                                                iptr++;
                                            sscanf(iptr, "%lg", &gval);
                                            *(tableptr->values + j * reftable->size1
                                                        + i) = gval * time_unit;
                                            while (*iptr != ' ' && *iptr != '\"' &&
                                                        *iptr != ',' || *iptr == '\\')
                                                iptr++;
                                        }
                                    }
                                }
                            }

                            token = advancetoken(flib, 0);
                            if (strcmp(token, ";"))
                                fprintf(stderr, "Failed to find end of value table\n");
			}
                        else if (!strcasecmp(token, "ecsm_waveform")) {
			    /* Not handled:  this takes the form of index_1 */
			    /* key : value or index_2 key : value and	    */
			    /* values key : value.			    */
			    token = advancetoken(flib, 0);	// Open parens
			    token = advancetoken(flib, ')');	// Close parens
			    token = advancetoken(flib, '{');	// Open brace
			    token = advancetoken(flib, '}');	// Close brace
			}
                        else if (!strcasecmp(token, "ecsm_capacitance")) {
			    /* Not handled:  this takes the form of index_1 */
			    /* key : value or index_2 key : value and	    */
			    /* values key : value.			    */
			    token = advancetoken(flib, 0);	// Open parens
			    token = advancetoken(flib, ')');	// Close parens
			    token = advancetoken(flib, '{');	// Open brace
			    token = advancetoken(flib, '}');	// Close brace
			}
                        else if (strcmp(token, "{") && strcmp(token, "}")) {
			    fprintf(stderr, "Unhandled feature %s at line %d\n",
					token, fileCurrentLine);
                            token = advancetoken(flib, 0);
			    if (!strcmp(token, "(")) {		 // Open parens
				token = advancetoken(flib, ')'); // Close parens
				token = advancetoken(flib, 0);
			    }
			    if (!strcmp(token, "{"))		 // Open brace
				token = advancetoken(flib, '}'); // Close brace
			}
                    }
                }
                else {
                    // For unhandled tokens, read in tokens.  If it is
                    // a definition or function, read to end-of-line.  If
                    // it is a block definition, read to end-of-block.
                    while (1) {
                        token = advancetoken(flib, 0);
                        if (token == NULL) break;
                        if (!strcmp(token, ";")) break;
                        if (!strcmp(token, "\""))
                            token = advancetoken(flib, '\"');
                        if (!strcmp(token, "{")) {
                            token = advancetoken(flib, '}');
                            break;
                        }
                    }
                }
                break;
        }
        token = advancetoken(flib, 0);
    }
}

/*--------------------------------------------------------------*/
/* Read a verilog netlist and collect information about the     */
/* cells instantiated and the network structure                 */
/*--------------------------------------------------------------*/

void
verilogRead(char *filename, cell *cells, net **netlist, instance **instlist,
                connect **inputlist, connect **outputlist, struct hashtable *Nethash)
{
    struct portrec  *port;
    struct instance *inst;
    struct cellrec  *topcell;
    struct netrec   *net;

    connptr newconn, testconn;
    instptr newinst;
    pinptr testpin;
    netptr newnet, testnet;
    cellptr testcell;

    int vstart, vtarget;

    /* Get verilog netlist structure using routines in readverilog.c */
    topcell = ReadVerilog(filename);
    if (topcell && topcell->name) {
	fprintf(stdout, "Parsing module \"%s\"\n", topcell->name);
    }

    /* Build local connection lists from returned netlist structure */

    for (port = topcell->portlist; port; port = port->next) {
	testconn = NULL;

	// Create a net entry for the input or output, add to the list of nets

	net = HashLookup(port->name, &topcell->nets);
	if (net->start == 0 && net->end == 0) {
	    newnet = create_net(netlist);
	    newnet->name = strdup(port->name);
	    HashPtrInstall(newnet->name, newnet, Nethash);

	    testconn = (connptr)malloc(sizeof(connect));
	    testconn->refnet = newnet;
	    testconn->refpin = NULL;        // No associated pin
	    testconn->refinst = NULL;       // No associated instance
	    testconn->tag = NULL;
	    testconn->metric = -1.0;
	    testconn->icDelay = 0.0;
	    testconn->visited = (unsigned char)0;
	    testconn->prvector = NULL;
	    testconn->pfvector = NULL;
	    testconn->trvector = NULL;
	    testconn->tfvector = NULL;

            if (port->direction == PORT_INPUT) {    // driver (input)
		testconn->next = *inputlist;
		*inputlist = testconn;
            }
            else {				    // receiver (output)
		testconn->next = *outputlist;
		*outputlist = testconn;
            }
	}
	else {
	    vtarget = net->end + ((net->start < net->end) ? 1 : -1);
	    vstart = net->start;
	    while (vstart != vtarget) {
		newnet = create_net(netlist);
		newnet->name = (char *)malloc(strlen(port->name) + 6);
		sprintf(newnet->name, "%s[%d]", port->name, vstart);
		HashPtrInstall(newnet->name, newnet, Nethash);

		vstart += (vtarget > net->end) ? 1 : -1;

		testconn = (connptr)malloc(sizeof(connect));
		testconn->refnet = newnet;
		testconn->refpin = NULL;    // No associated pin
		testconn->refinst = NULL;   // No associated instance
		testconn->tag = NULL;
		testconn->metric = -1.0;
		testconn->icDelay = 0.0;
		testconn->visited = (unsigned char)0;
		testconn->prvector = NULL;
		testconn->pfvector = NULL;
		testconn->trvector = NULL;
		testconn->tfvector = NULL;

		if (port->direction == PORT_INPUT) {    // driver (input)
		    testconn->next = *inputlist;
		    *inputlist = testconn;
		}
		else {                      // receiver (output)
		    testconn->next = *outputlist;
		    *outputlist = testconn;
		}
	    }
	}
    }

    for (inst = topcell->instlist; inst; inst = inst->next) {
        for (testcell = cells; testcell; testcell = testcell->next)
            if (!strcasecmp(testcell->name, inst->cellname))
                break;

	// NOTE:  testcell may be NULL for non-functional cells like
	// filler cells which have no I/O and so have no timing.  Only
	// report cells that are relevant to timing and do not show up
	// in the liberty database (portlist is non-NULL).

        if ((testcell == NULL) && (inst->portlist != NULL)) {
	    fprintf(stderr, "Cell \"%s\" was not in the liberty database!\n",
		    inst->cellname);
	    continue;
	}

        newinst = (instptr)malloc(sizeof(instance));
        newinst->next = *instlist;
        *instlist = newinst;
        newinst->refcell = testcell;
        newinst->in_connects = NULL;
        newinst->out_connects = NULL;
        newinst->name = strdup(inst->instname);

	for (port = inst->portlist; port; port = port->next) {
            newconn = (connptr)malloc(sizeof(connect));
            for (testpin = testcell->pins; testpin; testpin = testpin->next) {
                if (!strcmp(testpin->name, port->name))
                    break;
            }
            // Sanity check
            if (testpin == NULL) {
                fprintf(stderr, "No such pin \"%s\" in cell \"%s\"!\n",
                        port->name, testcell->name);
            }
            else {
                if (testpin->type & OUTPUT) {
                    newconn->next = newinst->out_connects;
                    newinst->out_connects = newconn;
                }
                else {
                    newconn->next = newinst->in_connects;
                    newinst->in_connects = newconn;
                }
            }
            newconn->refinst = newinst;
            newconn->refpin = testpin;
            newconn->refnet = NULL;
            newconn->tag = NULL;
            newconn->metric = -1.0;
	    newconn->icDelay = 0.0;
	    newconn->visited = (unsigned char)0;
            newconn->prvector = NULL;
            newconn->pfvector = NULL;
            newconn->trvector = NULL;
            newconn->tfvector = NULL;

            testnet = (netptr)HashLookup(port->net, Nethash);
            if (testnet == NULL) {
                // This is a new net, and we need to record it
                newnet = create_net(netlist);
                newnet->name = strdup(port->net);
                HashPtrInstall(newnet->name, newnet, Nethash);
                newconn->refnet = newnet;
            }
            else
                newconn->refnet = testnet;
	}
    }
    FreeVerilog(topcell);   // All structures transferred
}

/*--------------------------------------------------------------*/
/* For each net, go through the list of receivers and add the   */
/* contributions of each to the total load.  This is either     */
/* the input pin capacitance, if the receiver is a pin, or the  */
/* designated output load (given on the command line), if the   */
/* receiver is an output pin.                                   */
/*--------------------------------------------------------------*/

void
computeLoads(netptr netlist, instptr instlist, double out_load)
{
    instptr testinst;
    pinptr testpin;
    netptr testnet, driver, loadnet;
    connptr testconn;
    int i;

    for (testnet = netlist; testnet; testnet = testnet->next) {
        for (i = 0; i < testnet->fanout; i++) {
            testconn = testnet->receivers[i];
            testpin = testconn->refpin;
            if (testpin == NULL) {
                testnet->loadr += out_load;
                testnet->loadf += out_load;
            }
            else {
                testnet->loadr += testpin->capr;
                testnet->loadf += testpin->capf;
            }
        }
    }

    // For each instance input pin, collapse the pin's lookup table
    // to a vector by interpolating/extrapolating the table at the
    // calculated output load.  Save this vector in the connection
    // record for the pin.

    for (testinst = instlist; testinst; testinst = testinst->next) {
	double loadr, loadf;
	if (testinst->out_connects != NULL) {
	    loadnet = testinst->out_connects->refnet;
	    loadr = loadnet->loadr;
	    loadf = loadnet->loadf;
	}
	else {
	    loadr = 0.0;
	    loadf = 0.0;
	}
	     
	for (testconn = testinst->in_connects; testconn; testconn = testconn->next) {
	    testpin = testconn->refpin;

            if (testpin->propdelr)
                testconn->prvector = table_collapse(testpin->propdelr, loadr);
            if (testpin->propdelf)
                testconn->pfvector = table_collapse(testpin->propdelf, loadf);
            if (testpin->transr)
                testconn->trvector = table_collapse(testpin->transr, loadr);
            if (testpin->transf)
                testconn->tfvector = table_collapse(testpin->transf, loadf);
        }
    }
}

/*--------------------------------------------------------------*/
/* Assign types to each net.  This identifies which nets are    */
/* clock inputs, which are latch enable inputs, and which are   */
/* asynchronous set/reset inputs,.                              */
/*                                                              */
/* Whenever a clock input to a flop is found, add the           */
/* connection record to clockedlist                             */
/*                                                              */
/* For diagnostics, return the number of entries in clockedlist */
/*--------------------------------------------------------------*/

int assign_net_types(netptr netlist, connlistptr *clockedlist)
{
    int i, numterms;
    netptr testnet;
    connptr testrcvr;
    pinptr testpin;
    connlistptr newclocked;

    numterms = 0;

    for (testnet = netlist; testnet; testnet = testnet->next) {
	/* Nets with no fanout are by definition module outputs */
	if (testnet->fanout == 0) testnet->type |= OUTTERM;
        for (i = 0; i < testnet->fanout; i++) {
            testrcvr = testnet->receivers[i];
            testpin = testrcvr->refpin;
            if (testpin == NULL)
                testnet->type |= OUTTERM;
            else {
                switch (testpin->type & (DFFMASK | LATCHMASK)) {
                    case DFFCLK:
                        testnet->type |= CLOCK;
                        newclocked = (connlistptr)malloc(sizeof(connlist));
                        newclocked->connection = testrcvr;
                        newclocked->next = *clockedlist;
                        *clockedlist = newclocked;
                        numterms++;
                        break;
                    case DFFIN:
                        testnet->type |= TERMINAL;
                        break;
                    case DFFSET:
                    case DFFRST:
                        testnet->type |= ASYNC;
                        break;
                    case LATCHIN:
                        testnet->type |= LATCHTERM;
                        break;
                    case LATCHEN:
                        testnet->type |= ENABLE;
                        break;
                }
            }
        }
    }
    return numterms;
}

/*--------------------------------------------------------------*/
/* Create the links representing the netlist connections.       */
/*                                                              */
/* The verilogRead() routine added all nets and instances, and  */
/* for each instance, generated a list of net connections to    */
/* each pin.  To make the netlist easily traversible, this      */
/* routine does the following:                                  */
/*                                                              */
/* For each instance, work through the list of pin              */
/*      connections.  If the pin is an output, then add the     */
/*      connection as the net's driver entry.  If the pin is    */
/*      an input, then add the connection to the list of the    */
/*      net's receivers, and increment the net's fanout.        */
/*                                                              */
/* For each module input, add the input connection as the       */
/*      net's driver (flag an error if the net already has a    */
/*      driver).                                                */
/*                                                              */
/* For each module output, add the output connection as one of  */
/*      the net's receivers (it may be the only one).           */
/*                                                              */
/*--------------------------------------------------------------*/

void
createLinks(netptr netlist, instptr instlist, connptr inputlist, connptr outputlist)
{
    netptr testnet;
    instptr testinst;
    connptr testconn;

    for (testinst = instlist; testinst; testinst = testinst->next) {
        for (testconn = testinst->in_connects; testconn; testconn = testconn->next) {
            testnet = testconn->refnet;
            testnet->fanout++;
            if (testnet->receivers == NULL)
                testnet->receivers = (connptr *)malloc(sizeof(connptr));

            else
                testnet->receivers = (connptr *)realloc(testnet->receivers,
                                testnet->fanout * sizeof(connptr));

            testnet->receivers[testnet->fanout - 1] = testconn;
        }

        for (testconn = testinst->out_connects; testconn; testconn = testconn->next) {
            testnet = testconn->refnet;
            testnet->driver = testconn;
        }
    }

    for (testconn = inputlist; testconn; testconn = testconn->next) {
        testnet = testconn->refnet;
        if (testnet->driver != NULL)
            fprintf(stderr, "Error:  Input pin \"%s\" has an internal driver!\n",
                        testnet->name);
        // else
        //    testnet->driver = testconn;       // Don't do this, makes connectivity circular
    }

    for (testconn = outputlist; testconn; testconn = testconn->next) {
        testnet = testconn->refnet;
        testnet->fanout++;
        if (testnet->receivers == NULL)
            testnet->receivers = (connptr *)malloc(sizeof(connptr));

        else
            testnet->receivers = (connptr *)realloc(testnet->receivers,
                        testnet->fanout * sizeof(connptr));

        testnet->receivers[testnet->fanout - 1] = testconn;
    }
}

/*--------------------------------------------------------------*/
/* Delay comparison used by qsort() to sort paths in order from */
/* longest to shortest propagation delay.                       */
/*--------------------------------------------------------------*/

int
compdelay(ddataptr *a, ddataptr *b)
{
    ddataptr p = *a;
    ddataptr q = *b;

    if (p->delay < q->delay)
        return (1);
    if (p->delay > q->delay)
        return (-1);
    return (0);
}

void
delayRead(FILE *fdly, struct hashtable *Nethash)
{
    char c[128];
    char d[128];
    char *token;
    char *result;
    char *tokencopy = NULL;

    netptr newnet, testnet;
    connptr testconn;
    pinptr testpin;
    int i;
    int numRxers;

    if (debug == 1)
	fprintf(stdout, "delayRead\n");

    /* NOTE:  Do not use 0 for delimiter, or else ':' is considered to	*/
    /* be a standard delimiter, breaking up certain yosys-generated	*/
    /* net names.							*/
    
    token = advancetokennocont(fdly, '\n');
    result = token;

    while (token != NULL) {

        char *saveptr;
        char *saveptr2;

        numRxers = 0;
        testnet = (netptr)HashLookup(token, Nethash);

	/* Syntax cleanup for net renaming between various	*/
	/* netlist formats.					*/

	if ((testnet == NULL) && cleanup) {
	    char *mchr, *dchr;
	    /* Handle the insane backslash-escape names in verilog.
	     * To make these compatible with SPICE, qflow opts to
	     * replace the ending space character with another
	     * backslash.  The 2nd backslash has to be replaced by
	     * the original space character to match the original
	     * verilog net name.
	     */
	    
	    if (*token == '\\') {
	        if ((mchr = strchr(token + 1, '\\')) != NULL) {
		    dchr = strchr(token + 1, ' ');
		    if ((dchr == NULL) || (dchr > mchr)) *mchr = ' ';
		}
	    }
	    testnet = (netptr)HashLookup(token, Nethash);

	    /* Other, legacy stuff. */
	    if (testnet == NULL) {
		tokencopy = strdup(token);
		if ((mchr = strrchr(tokencopy, '<')) != NULL) {
		    if ((dchr = strrchr(tokencopy, '>')) != NULL) {
			if (mchr < dchr) {
			    *mchr = '[';
			    *dchr = ']';
			}
		    }
		}
		testnet = (netptr)HashLookup(tokencopy, Nethash);
	    }

	    if (testnet == NULL) {
		for (mchr = tokencopy; *mchr != '\0'; mchr++) {
		    if ((*mchr == ':') || (*mchr == '.') || (*mchr == '$')
				|| (*mchr == '<') || (*mchr == '>'))
			*mchr = '_';
		}
		testnet = (netptr)HashLookup(tokencopy, Nethash);
	    }

	    if (testnet == NULL) {
		for (mchr = tokencopy; *mchr != '\0'; mchr++) {
		    if ((*mchr == '[') || (*mchr == ']'))
			*mchr = '_';
		}
		testnet = (netptr)HashLookup(tokencopy, Nethash);
	    }
	}
	if (tokencopy != NULL) free(tokencopy);

	if (testnet == NULL) {
	    fprintf(stderr, "ERROR: Net %s not found in hash table\n", token);
	    exit(-1);
	}

        // Read driver of interconnect and total interconnect capacitance
        result = fgets(c, 128, fdly);
	if (result == NULL) break;

        strtok_r(c, "/", &saveptr);
        if (debug == 1)
            fprintf(stdout, "\tDriver Inst: %s\n", saveptr);
        strtok_r(NULL, " ", &saveptr);
        if (debug == 1) {
            fprintf(stdout, "\tDriver Pin: %s\n", saveptr);
            fprintf(stdout, "\tTotC: %f\n", strtof(saveptr, NULL));
	}

	if (c[1] == '\0') {
	    fprintf(stderr, "ERROR: Driver not found for net %s\n", testnet->name);
	}

        /* Load in .dly file is in pF, but we keep fF in loadr/loadf */
        testnet->loadr = (strtod(saveptr, NULL)) * 1e3;
        testnet->loadf = testnet->loadr;

        result = fgets(c, 128, fdly);
	if (result == NULL) break;

        while (c[0] != '\n') {
            if (debug == 1) fprintf(stdout, "\t%s\n", c);

            //separate receiver name and delay value
            strtok_r(c, " ", &saveptr);
	    if (debug == 1) {
                fprintf(stdout, "\tDelay: %s\n", saveptr);
                fprintf(stdout, "\tRxer Name: %s\n", c);
	    }
            strtok_r(c, "/", &saveptr2);
	    if (debug == 1) {
                fprintf(stdout, "\tRxer Name: %s\n", c);
                fprintf(stdout, "\tRxer Name: %s\n", saveptr2);
                fprintf(stdout, "\tDelay: %f\n", strtof(saveptr, NULL));
	    }

            for (i = 0; i < testnet->fanout; i++) {

                testconn = testnet->receivers[i];

                if (testnet->type == OUTTERM) {
		    if (debug == 1)
                        fprintf(stdout, "\tNet connects to output and has no "
				"receiving instance pin\n");
                    testconn->icDelay = strtod(saveptr, NULL);
                } else {
		    if (debug == 1)
                        fprintf(stdout, "\trefinstname: %s\n", testconn->refinst->name);

                    if (!strcmp(testconn->refinst->name, c)) {

                        testconn->icDelay = strtod(saveptr, NULL);
                        break;
                    }
                }
		if (debug == 1)
                    fprintf(stdout, "\tName: %s\n", c);
            }

            result = fgets(c, 128, fdly);
	    if (result == NULL) break;
            numRxers += 1;
        }
	if (result == NULL) break;

        if (numRxers != testnet->fanout) {
	    if (numRxers != 1 || testnet->fanout > 0 || testnet->type != OUTTERM)
		fprintf(stderr, "ERROR: Net %s had %d receiver%s in delay file, "
			" but expected a fanout of %d\n", testnet->name,
			numRxers, (numRxers == 1) ? "" : "s",
			testnet->fanout);
        }

        token = advancetokennocont(fdly, '\n');
    }
    if (result == NULL) {
	fprintf(stderr, "ERROR: Unexpected end-of-file while reading delay file.\n");
    }
}

/*--------------------------------------------------------------*/
/* Print a path component                                       */
/* (code contributed by Karl-Filip Faxen)			*/
/*--------------------------------------------------------------*/

void
print_path_component(int netFWidth, int instFWidth, int pinFWidth, int recvFWidth, 
                     btptr backtrace, FILE *file)
{
    // Return immediately if we have niether a driver or a receiver instance
    if (backtrace->receiver->refnet->driver == NULL &&
		backtrace->receiver->refinst == NULL) return;

    fprintf(file, " %8.1f ps", backtrace->delay);
    if (backtrace->receiver->refnet != NULL) {
        netptr net = backtrace->receiver->refnet;
        fprintf(file, "  %*s: ", netFWidth, net->name);
        if (net->driver != NULL) {   // If the driver exists, it has a refinst
            fprintf(file, "%*s/%*s",
                    instFWidth, net->driver->refinst->name,
                    -pinFWidth, net->driver->refpin->name);
        }
        else {
            fprintf(file, "%*s", instFWidth + pinFWidth + 1, "");
        }
    }
    fprintf(file, " -> ");
    if (backtrace->receiver->refinst != NULL) {
        fprintf(file, "%*s/%s",
                recvFWidth, backtrace->receiver->refinst->name,
                              backtrace->receiver->refpin->name);
    }
    else if (backtrace->receiver->refnet)
        fprintf(file, "%s", backtrace->receiver->refnet->name);

    fprintf(file, "\n");
}

/*--------------------------------------------------------------*/
/* Print a path                                                 */
/* (code contributed by Karl-Filip Faxen)			*/
/*--------------------------------------------------------------*/

void
print_path(btptr backtrace, FILE *file)
{
    int netFWidth = 0;
    int instFWidth = 0;
    int pinFWidth = 0;
    int recvFWidth = 0;
    btptr curr = backtrace, prev = NULL;

    // The back trace is last entry first, so we do one pointer reversal to
    // get it in first entry first order, then we reverse it back again.

    while (curr != NULL) {
        // Find max length of net name, inst name and pin name
        netptr net = curr->receiver->refnet;
        if (net != NULL) {
            int namelen = strlen(net->name);
            if (namelen > netFWidth) netFWidth = namelen;
            if (net->driver != NULL) { // If driver exists, it has an instance
                int instlen = strlen(net->driver->refinst->name);
                if (instlen > instFWidth) instFWidth = instlen;
                int pinlen = strlen(net->driver->refpin->name);
                if (pinlen > pinFWidth) pinFWidth = pinlen;
            }
        }
        // Find max length of receiving inst name
        if (curr->receiver->refinst != NULL) {
            int instlen = strlen(curr->receiver->refinst->name);
            if (instlen > recvFWidth) recvFWidth = instlen;
        }
        // Do the first pointer reversal
        btptr tmp = curr->next;
        curr->next = prev;
        prev = curr;
        curr = tmp;
    }

    curr = prev;
    prev = NULL;

    while (curr != NULL) {
        print_path_component(netFWidth, instFWidth, pinFWidth, recvFWidth, curr, file);
        // Do the second pointer reversal
        btptr tmp = curr->next;
        curr->next = prev;
        prev = curr;
        curr = tmp;
    }
    fprintf(file, "\n");
}

/*--------------------------------------------------------------*/
/* Given a list of paths, find the clock at the source and the	*/
/* destination (if neither is a pin), and compute the clock	*/
/* skew between them.  Also compute the setup or hold at the	*/
/* destination.	 Save these values in the path record.		*/
/*--------------------------------------------------------------*/

void
find_clock_skews(ddataptr pathlist, char minmax)
{
    connptr testconn, thisconn;
    instptr  testinst;

    ddataptr testddata, srcddata, freeddata;
    ddataptr clocklist, clock2list;
    btptr    backtrace, freebt, pathbt, btcommon;
    btptr    selectedsource, selecteddest;

    short srcdir, destdir;		// Signal direction in/out
    double setupdelay, holddelay;
    unsigned char result;
    char	clk_invert, clk_sense_inv;

    clocklist = NULL;
    clock2list = NULL;
    
    for (testddata = pathlist; testddata; testddata = testddata->next) {

        // Find the end of the linked list, which is the path start.
        for (pathbt = testddata->backtrace; pathbt->next; pathbt = pathbt->next);
	thisconn = pathbt->receiver;

        if (thisconn && thisconn->refpin) {
            // Find the sources of the clock at the path start.  Create
	    // a tree of backtraces from thisconn to all clock sources,
	    // and return clocklist, which is a list of the sources.

            find_clock_source(thisconn, &clocklist, NULL, srcdir, (unsigned char)1);

            // Calculate the worst-case transition times to testlink on
	    // each clocklist backtrace.

            find_clock_transition(clocklist, thisconn, srcdir, minmax,
			(unsigned char)1);
	}

        // Copy last backtrace delay to testddata.
        testddata->delay = testddata->backtrace->delay;
        testddata->trans = testddata->backtrace->trans;
        testinst = testddata->backtrace->receiver->refinst;
        selecteddest = selectedsource = NULL;

        if (testinst != NULL) {
            // Find the sources of the clock at the path end
            destdir = (testinst->refcell->type & CLK_SENSE_MASK) ? FALLING : RISING;
            testconn = find_register_clock(testinst);
            // If testconn is NULL, this is not a register (latch, maybe?)
            if (testconn == NULL) continue;

            // Find the connection that is common to both clocks
            result = find_clock_source(testconn, &clock2list, NULL, destdir,
			(unsigned char)2);

	    if ((result == (unsigned char)0) && (clocklist != NULL)) {
		// If both paths end on the same input net, then there
		// is no connection pointer, so deal with that separately.

		if (clocklist->backtrace->receiver->refnet !=
			    	clock2list->backtrace->receiver->refnet) {

		    // Warn about asynchronous clock sources
		    if (verbose > 1) {
		 	fflush(stdout);
			fprintf(stderr, "Independent clock nets \"%s\" and \"%s\""
					" drive related gates!\n",
					testconn->refnet->name,
					thisconn->refnet->name);
		    }
		    clk_invert = -1;
		}
            }

	    // If clocklist is NULL then this is an input and there is
	    // no way to compute relative to a common clock, because
	    // there is no common clock.

	    if (clocklist != NULL) {
		// Find clock arrival times from common clock point.  Note that
		// the check is opposite to what is computed for the source;  if
		// maximum time was used to find clock-to-source, then minimum
		// time is used to find clock-to-destination.

		btcommon = find_common_clock(clock2list, clocklist);
                find_clock_transition(clock2list, testconn,
				(btcommon) ? btcommon->dir : RISING,
				~minmax, (unsigned char)2);

		// selectedsource is the end of the btcommon backtrace
		for (selectedsource = (btcommon) ? btcommon : clocklist->backtrace;
				selectedsource->next;
				selectedsource = selectedsource->next);
		// selecteddest is the end of the clock2list backtrace
		for (selecteddest = clock2list->backtrace; selecteddest->next;
				selecteddest = selecteddest->next);

                // Add or subtract difference in arrival times between source and
                // destination clocks

                testddata->skew = selectedsource->delay - selecteddest->delay -
					((btcommon) ? btcommon->delay : 0.0);
                testddata->delay += testddata->skew;
	    }
	    else if (clock2list) {
		// Still need to know destination's clock source to
		// calculate setup or hold time.
		for (selecteddest = clock2list->backtrace; selecteddest->next;
				selecteddest = selecteddest->next);
		testddata->skew = 0.0;
	    }

            if (minmax == MAXIMUM_TIME) {
                // Add setup time for destination clocks
                setupdelay = calc_setup_time(testddata->trans,
                                        testddata->backtrace->receiver->refpin,
                                        selecteddest->trans,
                                        testddata->backtrace->dir, minmax);
                testddata->setup = setupdelay;
            }
            else {
                // Subtract hold time for destination clocks (note that hold times
		// are defined as typically negative values in the liberty format)
                holddelay = calc_hold_time(testddata->trans,
                                        testddata->backtrace->receiver->refpin,
                                        selecteddest->trans,
                                        testddata->backtrace->dir, minmax);
                testddata->setup = holddelay;
            }
            testddata->delay += testddata->setup;

            if (verbose > 1)
                fprintf(stdout, "Path terminated on flop \"%s\" input with max delay %g ps\n",
                                testconn->refinst->name, testddata->delay);

            for (backtrace = testddata->backtrace; backtrace->next;
                                backtrace = backtrace->next) {
                if (verbose > 1)
                    fprintf(stdout, "   %g (%s) %s/%s -> %s/%s\n",
                                backtrace->delay,
                                backtrace->receiver->refnet->name,
                                backtrace->receiver->refnet->driver->refinst->name,
                                backtrace->receiver->refnet->driver->refpin->name,
                                backtrace->receiver->refinst->name,
                                backtrace->receiver->refpin->name);
            }
            if (verbose > 1)
                fprintf(stdout, "   000.000 (%s) %s/%s -> %s/%s\n",
                                backtrace->receiver->refnet->name,
                                backtrace->receiver->refinst->name,
                                backtrace->receiver->refpin->name,
                                backtrace->receiver->refinst->name,
                                backtrace->receiver->refinst->out_connects->refpin->name);

            if (selecteddest != NULL && selectedsource != NULL) {
                if (verbose > 1) {
                    if (selectedsource->receiver->refnet != selecteddest->receiver->refnet) {
                        fprintf(stdout, "   %g %s to %s clock skew\n",
                                selecteddest->delay - selectedsource->delay
				+ ((btcommon) ? btcommon->delay : 0.0),
                                selectedsource->receiver->refnet->name,
                                selecteddest->receiver->refnet->name);
                    }
                }

                /* Check if the flops have the same clock sense */
                /* (both are clock rising edge or both are clock falling edge type) */

		if (backtrace->receiver->refinst == NULL)
		    clk_sense_inv = 1;
                else if ((testinst->refcell->type & CLK_SENSE_MASK) !=
                                        (backtrace->receiver->refinst->refcell->type
                                        & CLK_SENSE_MASK))
                    clk_sense_inv = 1;
                else
                    clk_sense_inv = 0;

                /* If the two flops don't clock at the same time, then issue a  */
                /* warning that the slack time loses half a clock period.       */

                if ((verbose > 1) && (clk_invert != -1) && (clk_sense_inv != clk_invert)) {
                    fprintf(stdout, "   Clocks are inverted relative to one another,\n");
                    fprintf(stdout, "   implying a maximum propagation delay of 1/2 period.\n");
                }
            }
            if (thisconn != NULL && testconn != NULL) {
                if (verbose > 1) {
                    if (minmax == MAXIMUM_TIME)
                        fprintf(stdout, "   %g setup time at destination\n", setupdelay);
                    else
                        fprintf(stdout, "   %g hold time at destination\n", holddelay);
                }
            }

            if (verbose > 1) fprintf(stdout, "\n");
        }
        else if (verbose > 1) {
            fprintf(stdout, "Path terminated on output \"%s\" with max delay %g ps\n",
                        testddata->backtrace->receiver->refnet->name, testddata->delay);

            backtrace = testddata->backtrace;
            fprintf(stdout, "   %g (%s) %s/%s -> [output pin]\n",
                        backtrace->delay,
                        backtrace->receiver->refnet->name,
                        backtrace->receiver->refnet->driver->refinst->name,
                        backtrace->receiver->refnet->driver->refpin->name);

            for (backtrace = backtrace->next; backtrace->next; backtrace = backtrace->next) {
                fprintf(stdout, "   %g (%s) %s/%s -> %s/%s\n",
                        backtrace->delay,
                        backtrace->receiver->refnet->name,
                        backtrace->receiver->refnet->driver->refinst->name,
                        backtrace->receiver->refnet->driver->refpin->name,
                        backtrace->receiver->refinst->name,
                        backtrace->receiver->refpin->name);
            }
            fprintf(stdout, "   000.000 (%s) %s/%s -> %s/%s\n\n",
                        backtrace->receiver->refnet->name,
                        backtrace->receiver->refinst->name,
                        backtrace->receiver->refpin->name,
                        backtrace->receiver->refinst->name,
                        backtrace->receiver->refinst->out_connects->refpin->name);
        }

        // Clean up clock2list backtraces
	for (freeddata = clock2list; freeddata; freeddata = freeddata->next) {
	    while (freeddata->backtrace != NULL) {
		freebt = freeddata->backtrace;
		testconn = freebt->receiver;
		freeddata->backtrace = freeddata->backtrace->next;
                freebt->refcnt--;
		if (freebt->refcnt == 0) free(freebt);
		else break;
		if (testconn->visited != (unsigned char)2)
		    break;
		testconn->visited = (unsigned char)0;
	    }
	}

        // Clean up clock2list
        while (clock2list != NULL) {
            freeddata = clock2list;
            clock2list = clock2list->next;
            free(freeddata);
        }

        // Free up clocklist backtraces
	for (freeddata = clocklist; freeddata; freeddata = freeddata->next) {
            while (freeddata->backtrace != NULL) {
		freebt = freeddata->backtrace;
		testconn = freebt->receiver;
		freeddata->backtrace = freeddata->backtrace->next;
                freebt->refcnt--;
		if (freebt->refcnt == 0) free(freebt);
		else break;
		if (testconn->visited != (unsigned char)1)
		    break;
		testconn->visited = (unsigned char)0;
	    }
	}

        // Free up clocklist
        while (clocklist != NULL) {
            freeddata = clocklist;
            clocklist = clocklist->next;
            free(freeddata);
        }
    }
}

/*--------------------------------------------------------------*/
/* Main program                                                 */
/*--------------------------------------------------------------*/

int
main(int objc, char *argv[])
{
    FILE *flib;
    FILE *fsrc;
    FILE *fdly;
    FILE *fsum;
    double period = 0.0;
    double outLoad = 0.0;
    double inTrans = 0.0;
    char *delayfile = NULL;
    char *summaryfile = NULL;
    char *summarydir = NULL;
    int ival, firstarg = 1;
    int longFormat = 0;    // Is the long format option present
    int numReportPaths = 20;

    // Liberty database

    lutable *tables = NULL;
    cell *cells = NULL;
    lutable *scalar;

    // Verilog netlist database

    instptr     instlist = NULL;
    netptr      netlist = NULL;
    connlistptr clockconnlist = NULL;
    connlistptr newinputconn, inputconnlist = NULL;
    connptr     testconn, inputlist = NULL;
    connptr     outputlist = NULL;

    // Timing path database
    ddataptr    pathlist = NULL;
    ddataptr    freeddata, testddata, *orderedpaths;
    btptr       freebt, testbt;
    int         numpaths, numterms, i;
    char        badtiming;
    double      slack;

    // Net name hash table
    struct hashtable Nethash;

    verbose = 0;
    exhaustive = 0;
    cleanup = 0;

    while ((firstarg < objc) && (*argv[firstarg] == '-')) {
       if (!strcmp(argv[firstarg], "-d") || !strcmp(argv[firstarg], "--delay")) {
          delayfile = strdup(argv[firstarg + 1]);
          firstarg += 2;
       }
       else if (!strcmp(argv[firstarg], "-p") || !strcmp(argv[firstarg], "--period")) {
          period = strtod(argv[firstarg + 1], NULL);
          firstarg += 2;
       }
       else if (!strcmp(argv[firstarg], "-l") || !strcmp(argv[firstarg], "--load")) {
          outLoad = strtod(argv[firstarg + 1], NULL);
          firstarg += 2;
       }
       else if (!strcmp(argv[firstarg], "-t") || !strcmp(argv[firstarg], "--trans")) {
          inTrans = strtod(argv[firstarg + 1], NULL);
          firstarg += 2;
       }
       else if (!strcmp(argv[firstarg], "-L") || !strcmp(argv[firstarg], "--long")) {
          longFormat = 1;
          firstarg++;
       }
       else if (!strcmp(argv[firstarg], "-n") || !strcmp(argv[firstarg], "--num-paths")) {
	  numReportPaths = strtod(argv[firstarg + 1], NULL);
	  firstarg += 2;
       }
       else if (!strcmp(argv[firstarg], "-s") || !strcmp(argv[firstarg], "--summary")) {
          summaryfile = strdup(argv[firstarg + 1]);
          /* Rule:  If argument has a file extension, then treat it as a single	*/
	  /* file and dump all output to it.  If not, then treat it as a	*/
	  /* directory and create individual files for each timing check.	*/
          if (strrchr(summaryfile, '.') == NULL) {
	     summarydir = summaryfile;
	     summaryfile = NULL;
	     /* Make directory if it doesn't exist */
	     mkdir(summarydir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
	  }
          firstarg += 2;
       }
       else if (!strcmp(argv[firstarg], "-v") || !strcmp(argv[firstarg], "--verbose")) {
          sscanf(argv[firstarg + 1], "%d", &ival);
          verbose = (unsigned char)ival;
          firstarg += 2;
       }
       else if (!strcmp(argv[firstarg], "-D") || !strcmp(argv[firstarg], "--debug")) {
          sscanf(argv[firstarg + 1], "%d", &ival);
          debug = (unsigned char)ival;
          firstarg += 2;
       }
       else if (!strcmp(argv[firstarg], "-e") || !strcmp(argv[firstarg], "--exhaustive")) {
          exhaustive = 1;
          firstarg++;
       }
       else if (!strcmp(argv[firstarg], "-c") || !strcmp(argv[firstarg], "--cleanup")) {
          cleanup = 1;
          firstarg++;
       }
       else if (!strcmp(argv[firstarg], "-V") || !strcmp(argv[firstarg], "--version")) {
          fprintf(stderr, "Vesta Static Timing Analyzer version 0.3\n");
          exit(0);
       }
       else {
          fprintf(stderr, "Unknown option \"%s\"\n", argv[firstarg]);
          firstarg++;
       }
    }

    if (objc - firstarg < 2) {
        fprintf(stderr, "Usage:  vesta [options] <name.v> <name.lib> [...]\n");
        fprintf(stderr, "Options:\n");
        fprintf(stderr, "--delay <delay_file>   or      -d <delay_file>\n");
        fprintf(stderr, "--period <period>      or      -p <period>\n");
        fprintf(stderr, "--load <load>          or      -l <load>\n");
	fprintf(stderr, "--num-paths <numPaths>	or	-n <numPaths>\n");
        fprintf(stderr, "--long                 or      -L\n");
        fprintf(stderr, "--verbose <level>      or      -v <level>\n");
        fprintf(stderr, "--exhaustive           or      -e\n");
        fprintf(stderr, "--cleanup              or      -c\n");
        fprintf(stderr, "--version              or      -V\n");
        exit (1);
    }
    else {
        fflush(stdout);
        fprintf(stdout, "----------------------------------------------\n");
        fprintf(stdout, "Vesta static timing analysis tool\n");
	fprintf(stdout, "for qflow " QFLOW_VERSION "." QFLOW_REVISION "\n");
        fprintf(stdout, "(c) 2013-2018 Tim Edwards, Open Circuit Design\n");
        fprintf(stdout, "----------------------------------------------\n\n");
        fflush(stdout);
    }

    fsrc = fopen(argv[firstarg], "r");
    if (fsrc == NULL) {
        fprintf(stderr, "Cannot open %s for reading\n", argv[firstarg]);
        exit (1);
    }
    fclose(fsrc);

    /*------------------------------------------------------------------*/
    /* Generate one table template for the "scalar" case                */
    /*------------------------------------------------------------------*/

    scalar = (lutable *)malloc(sizeof(lutable));
    scalar->name = strdup("scalar");
    scalar->invert = 0;
    scalar->var1 = CONSTRAINED_TIME;
    scalar->var2 = OUTPUT_CAP;
    scalar->size1 = 1;
    scalar->size2 = 1;
    scalar->idx1.times = (double *)malloc(sizeof(double));
    scalar->idx2.caps = (double *)malloc(sizeof(double));
    scalar->values = (double *)malloc(sizeof(double));

    scalar->idx1.times[0] = 0.0;
    scalar->idx2.caps[0] = 0.0;
    scalar->values[0] = 0.0;

    scalar->next = NULL;
    tables = scalar;

    /*------------------------------------------------------------------*/
    /* Read all liberty format files (everything on the command line	*/
    /* after the verilog source file).					*/
    /*------------------------------------------------------------------*/

    for (i = 1; firstarg + i < objc; i++) {

	flib = fopen(argv[firstarg + i], "r");
	if (flib == NULL) {
	    fprintf(stderr, "Cannot open %s for reading\n", argv[firstarg + i]);
	    exit (1);
	}

	/*------------------------------------------------------------------*/
	/* Read the liberty format file.  This is not a rigorous parser!    */
	/*------------------------------------------------------------------*/

	fileCurrentLine = 0;
	libertyRead(flib, &tables, &cells);
	fflush(stdout);
	fprintf(stdout, "Lib read %s:  Processed %d lines.\n", argv[firstarg + i],
			fileCurrentLine);
	if (flib != NULL) fclose(flib);
    }

    /*--------------------------------------------------*/
    /* Debug:  Print summary of liberty database        */
    /*--------------------------------------------------*/

    if (verbose > 3) {

        lutable *newtable;
        cell *newcell;
        pin *newpin;

        for (newtable = tables; newtable; newtable = newtable->next) {
            fprintf(stdout, "Table: %s\n", newtable->name);
        }

        for (newcell = cells; newcell; newcell = newcell->next) {
            fprintf(stdout, "Cell: %s\n", newcell->name);
            fprintf(stdout, "   Function: %s\n", newcell->function);
            for (newpin = newcell->pins; newpin; newpin = newpin->next) {
                if (newpin->type == INPUT)
                    fprintf(stdout, "   Pin: %s  cap=%g\n", newpin->name, newpin->capr);
            }
            fprintf(stdout, "\n");
        }
    }

    /*------------------------------------------------------------------*/
    /* Read verilog netlist.  This is also not a rigorous parser!       */
    /*------------------------------------------------------------------*/

    /* See hash.c for these routines and variables */
    hashfunc = hash;
    matchfunc = match;

    /* Initialize net hash table */
    InitializeHashTable(&Nethash, LARGEHASHSIZE);

    fileCurrentLine = 0;

    verilogRead(argv[firstarg], cells, &netlist, &instlist, &inputlist, &outputlist,
		&Nethash);

    if (delayfile != NULL) {
        fdly = fopen(delayfile, "r");

        if (fdly == NULL) {
            fprintf(stderr, "Cannot open %s for reading\n", delayfile);
            exit (1);
        }
    }
    else
	fdly = NULL;

    fflush(stdout);
    fprintf(stdout, "Verilog netlist read:  Processed %d lines.\n", vlinenum);

    /*--------------------------------------------------*/
    /* Debug:  Print summary of verilog source          */
    /*--------------------------------------------------*/

    if (verbose > 2) {
        connect *testoutput;
        connect *testinput;
        net *testnet;
        instance *testinst;

        for (testinput = inputlist; testinput; testinput = testinput->next) {
            if (testinput->refnet)
                fprintf(stdout, "   Input: %s\n", testinput->refnet->name);
        }
        for (testoutput = outputlist; testoutput; testoutput = testoutput->next) {
            if (testoutput->refnet)
                fprintf(stdout, "   Output: %s\n", testoutput->refnet->name);
        }
        for (testnet = netlist; testnet; testnet = testnet->next) {
            fprintf(stdout, "   Net: %s\n", testnet->name);
        }
        for (testinst = instlist; testinst; testinst = testinst->next) {
            fprintf(stdout, "   Gate: %s\n", testinst->name);
        }
    }

    /*--------------------------------------------------*/
    /* Generate internal links representing the network */
    /*--------------------------------------------------*/

    createLinks(netlist, instlist, inputlist, outputlist);

    /* Generate a connection list from inputlist */

    for (testconn = inputlist; testconn; testconn = testconn->next) {
        newinputconn = (connlistptr)malloc(sizeof(connlist));
        newinputconn->connection = testconn;
        newinputconn->next = inputconnlist;
        inputconnlist = newinputconn;
    }

    /*--------------------------------------------------*/
    /* Assign net types, mainly to identify clocks      */
    /* Return a list of clock nets                      */
    /*--------------------------------------------------*/

    numterms = assign_net_types(netlist, &clockconnlist);

    if (verbose > 2)
        fprintf(stdout, "Number of terminals to check: %d\n", numterms);

    /*--------------------------------------------------*/
    /* Calculate total load on each net                 */
    /* To do:  Add wire models or computed wire delays  */
    /*--------------------------------------------------*/

    if (fdly != NULL) {
        delayRead(fdly, &Nethash);
        fclose(fdly);
    }

    /* Hash table no longer needed */
    HashKill(&Nethash);

    computeLoads(netlist, instlist, outLoad);

    /*--------------------------------------------------*/
    /* Identify all clock-to-terminal paths             */
    /*--------------------------------------------------*/

    if (verbose > 0) {
	fprintf(stdout, "Starting timing analysis.\n");
	fflush(stdout);
    }
    numpaths = find_clock_to_term_paths(clockconnlist, &pathlist, netlist, MAXIMUM_TIME);
    fprintf(stdout, "Number of paths analyzed:  %d\n", numpaths);
    fflush(stdout);

    /* For each terminal path, find the source and destination clocks	*/
    /* (where they exist) and compute clock skew.  Also compute setup	*/
    /* or hold at the destination.					*/

    find_clock_skews(pathlist, MAXIMUM_TIME);

    /*--------------------------------------------------*/
    /* Collect paths into a non-linked array so that    */
    /* they can be sorted by delay time                 */
    /*--------------------------------------------------*/

    orderedpaths = (ddataptr *)malloc(numpaths * sizeof(ddataptr));

    i = 0;
    for (testddata = pathlist; testddata; testddata = testddata->next) {
       orderedpaths[i] = testddata;
       i++;
    }

    qsort(orderedpaths, numpaths, sizeof(ddataptr), (__compar_fn_t)compdelay);

    /*----------------------------------------------------*/
    /* Report on top <numReportPaths> maximum delay paths */
    /*----------------------------------------------------*/

    if (summarydir != NULL) {
        summaryfile = (char *)malloc(256);
	sprintf(summaryfile, "%s/reg_to_reg_max.log", summarydir);
    }
    if (summaryfile != NULL) {
        fsum = fopen(summaryfile, "w");
        if (fsum == NULL) {
            fprintf(stderr, "Cannot open %s for writing\n", summaryfile);
	    if (summarydir == NULL) {
 		free(summaryfile);
		summaryfile = NULL;
	    }
        }
    }
    else
	fsum = NULL;
    if (fsum)
	fprintf(fsum, "Vesta static timing analysis, "
			"register-to-register maximum timing\n");

    fprintf(stdout, "\nTop %d maximum delay paths:\n", (numpaths >= numReportPaths)
		? numReportPaths : numpaths);
    if (fsum)
	fprintf(fsum, "\nTop %d maximum delay paths:\n", (numpaths >= numReportPaths)
		? numReportPaths : numpaths);
    badtiming = 0;
    for (i = 0; ((i < numReportPaths) && (i < numpaths)); i++) {
        testddata = orderedpaths[i];
        for (testbt = testddata->backtrace; testbt->next; testbt = testbt->next);

        if (testddata->backtrace->receiver->refinst != NULL) {
            fprintf(stdout, "Path %s/%s to %s/%s delay %g ps",
                        testbt->receiver->refinst->name,
                        testbt->receiver->refpin->name,
                        testddata->backtrace->receiver->refinst->name,
                        testddata->backtrace->receiver->refpin->name,
                        testddata->delay);
            if (fsum) fprintf(fsum, "Path %s/%s to %s/%s delay %g ps",
                        testbt->receiver->refinst->name,
                        testbt->receiver->refpin->name,
                        testddata->backtrace->receiver->refinst->name,
                        testddata->backtrace->receiver->refpin->name,
                        testddata->delay);
        }
        else {
            fprintf(stdout, "Path %s/%s to output pin %s delay %g ps",
                        testbt->receiver->refinst->name,
                        testbt->receiver->refpin->name,
                        testddata->backtrace->receiver->refnet->name,
                        testddata->delay);
            if (fsum) fprintf(fsum, "Path %s/%s to output pin %s delay %g ps",
                        testbt->receiver->refinst->name,
                        testbt->receiver->refpin->name,
                        testddata->backtrace->receiver->refnet->name,
                        testddata->delay);
        }

        if (period > 0.0) {
            slack = period - testddata->delay;
            fprintf(stdout, "   Slack = %g ps", slack);
            if (fsum) fprintf(fsum, "   Slack = %g ps", slack);
            if (slack < 0.0) badtiming = 1;
        }
        fprintf(stdout, "\n");
        if (fsum) fprintf(fsum, "\n");
        if (longFormat) print_path(testddata->backtrace, stdout);
        if (fsum) print_path(testddata->backtrace, fsum);

        if (testddata->backtrace->receiver->refinst != NULL) {
	    if (longFormat) {
		fprintf(stdout, "   clock skew at destination = %g\n", testddata->skew);
		fprintf(stdout, "   setup at destination = %g\n", testddata->setup);
		fprintf(stdout, "\n");
	    }
	    if (fsum) {
		fprintf(fsum, "   clock skew at destination = %g\n", testddata->skew);
		fprintf(fsum, "   setup at destination = %g\n", testddata->setup);
		fprintf(fsum, "\n");
	    }
	}
    }

    if (period > 0.0) {
        if (badtiming) {
            fprintf(stdout, "ERROR:  Design fails timing requirements.\n");
            if (fsum) fprintf(fsum, "ERROR:  Design fails timing requirements.\n");
        }
        else {
            fprintf(stdout, "Design meets timing requirements.\n");
            if (fsum) fprintf(fsum, "Design meets timing requirements.\n");
        }
    }
    else if ((numpaths > 0) && (orderedpaths[0] != NULL)) {
        fprintf(stdout, "Computed maximum clock frequency (zero margin) = %g MHz\n",
                (1.0E6 / orderedpaths[0]->delay));
        if (fsum) fprintf(fsum, "Computed maximum clock frequency "
			"(zero margin) = %g MHz\n",
                	(1.0E6 / orderedpaths[0]->delay));
    }
    fprintf(stdout, "-----------------------------------------\n\n");
    if (fsum) fprintf(fsum, "-----------------------------------------\n\n");
    fflush(stdout);

    /*--------------------------------------------------*/
    /* Clean up the path list                           */
    /*--------------------------------------------------*/

    while (pathlist != NULL) {
        freeddata = pathlist;
        pathlist = pathlist->next;
        while (freeddata->backtrace != NULL) {
            freebt = freeddata->backtrace;
            freeddata->backtrace = freeddata->backtrace->next;
            freebt->refcnt--;
            if (freebt->refcnt == 0) free(freebt);
	    else break;
        }
        free(freeddata);
    }

    free(orderedpaths);

    /*--------------------------------------------------*/
    /* Now calculate minimum delay paths                */
    /*--------------------------------------------------*/

    reset_all(netlist, MINIMUM_TIME);
    numpaths = find_clock_to_term_paths(clockconnlist, &pathlist, netlist, MINIMUM_TIME);
    fprintf(stdout, "Number of paths analyzed:  %d\n", numpaths);
    fflush(stdout);

    find_clock_skews(pathlist, MINIMUM_TIME);

    /*--------------------------------------------------*/
    /* Collect paths into a non-linked array so that    */
    /* they can be sorted by delay time                 */
    /*--------------------------------------------------*/

    orderedpaths = (ddataptr *)malloc(numpaths * sizeof(ddataptr));

    i = 0;
    for (testddata = pathlist; testddata; testddata = testddata->next) {
       orderedpaths[i] = testddata;
       i++;
    }

    qsort(orderedpaths, numpaths, sizeof(ddataptr), (__compar_fn_t)compdelay);

    /*----------------------------------------------------*/
    /* Report on top <numReportPaths> minimum delay paths */
    /*----------------------------------------------------*/

    if (summarydir != NULL) {
        if (fsum != NULL) fclose(fsum);
	sprintf(summaryfile, "%s/reg_to_reg_min.log", summarydir);
        fsum = fopen(summaryfile, "w");
        if (fsum == NULL) {
	    fprintf(stderr, "Cannot open %s for writing\n", summaryfile);
	}
    }
    if (fsum)
	fprintf(fsum, "Vesta static timing analysis, "
			"register-to-register minimum timing\n");

    fprintf(stdout, "\nTop %d minimum delay paths:\n", (numpaths >= numReportPaths) ?
		numReportPaths : numpaths);
    if (fsum) fprintf(fsum, "\nTop %d minimum delay paths:\n",
		(numpaths >= numReportPaths) ?  numReportPaths : numpaths);
    badtiming = 0;
    for (i = numpaths; (i > (numpaths - numReportPaths)) && (i > 0); i--) {
        testddata = orderedpaths[i - 1];
        for (testbt = testddata->backtrace; testbt->next; testbt = testbt->next);

        if (testddata->backtrace->receiver->refinst != NULL) {
            fprintf(stdout, "Path %s/%s to %s/%s delay %g ps\n",
                        testbt->receiver->refinst->name,
                        testbt->receiver->refpin->name,
                        testddata->backtrace->receiver->refinst->name,
                        testddata->backtrace->receiver->refpin->name,
                        testddata->delay);
            if (fsum) fprintf(fsum, "Path %s/%s to %s/%s delay %g ps\n",
                        testbt->receiver->refinst->name,
                        testbt->receiver->refpin->name,
                        testddata->backtrace->receiver->refinst->name,
                        testddata->backtrace->receiver->refpin->name,
                        testddata->delay);
        }
        else {
            fprintf(stdout, "Path %s/%s to output pin %s delay %g ps\n",
                        testbt->receiver->refinst->name,
                        testbt->receiver->refpin->name,
                        testddata->backtrace->receiver->refnet->name,
                        testddata->delay);
            if (fsum) fprintf(fsum, "Path %s/%s to output pin %s delay %g ps\n",
                        testbt->receiver->refinst->name,
                        testbt->receiver->refpin->name,
                        testddata->backtrace->receiver->refnet->name,
                        testddata->delay);
        }
        if (longFormat) print_path(testddata->backtrace, stdout);
        if (fsum) print_path(testddata->backtrace, fsum);

	/* Print skew and hold unless destination is a pin */
        if (testddata->backtrace->receiver->refinst != NULL) {
	    if (longFormat) {
		fprintf(stdout, "   clock skew at destination = %g\n", testddata->skew);
		fprintf(stdout, "   hold at destination = %g\n", testddata->setup);
		fprintf(stdout, "\n");
	    }
	    if (fsum) {
		fprintf(fsum, "   clock skew at destination = %g\n", testddata->skew);
		fprintf(fsum, "   hold at destination = %g\n", testddata->setup);
		fprintf(fsum, "\n");
	    }
	}

        if (testddata->delay < 0.0) badtiming = 1;
    }
    if (badtiming) {
        fprintf(stdout, "ERROR:  Design fails minimum hold timing.\n");
        if (fsum) fprintf(fsum, "ERROR:  Design fails minimum hold timing.\n");
    }
    else {
        fprintf(stdout, "Design meets minimum hold timing.\n");
        if (fsum) fprintf(fsum, "Design meets minimum hold timing.\n");
    }

    fprintf(stdout, "-----------------------------------------\n\n");
    if (fsum) fprintf(fsum, "-----------------------------------------\n\n");
    fflush(stdout);

    /*--------------------------------------------------*/
    /* Clean up the path list                           */
    /*--------------------------------------------------*/

    while (pathlist != NULL) {
        freeddata = pathlist;
        pathlist = pathlist->next;
        while (freeddata->backtrace != NULL) {
            freebt = freeddata->backtrace;
            freeddata->backtrace = freeddata->backtrace->next;
            freebt->refcnt--;
            if (freebt->refcnt == 0) free(freebt);
	    else break;
        }
        free(freeddata);
    }

    free(orderedpaths);

    for (testconn = inputlist; testconn; testconn = testconn->next) {
        testconn->tag = NULL;
        testconn->metric = -1;
    }

    /*--------------------------------------------------*/
    /* Identify all input-to-terminal paths             */
    /*--------------------------------------------------*/

    reset_all(netlist, MAXIMUM_TIME);
    numpaths = find_clock_to_term_paths(inputconnlist, &pathlist, netlist, MAXIMUM_TIME);
    fprintf(stdout, "Number of paths analyzed:  %d\n", numpaths);
    fflush(stdout);

    find_clock_skews(pathlist, MAXIMUM_TIME);

    /*--------------------------------------------------*/
    /* Collect paths into a non-linked array so that    */
    /* they can be sorted by delay time                 */
    /*--------------------------------------------------*/

    orderedpaths = (ddataptr *)malloc(numpaths * sizeof(ddataptr));

    i = 0;
    for (testddata = pathlist; testddata; testddata = testddata->next) {
       orderedpaths[i] = testddata;
       i++;
    }

    qsort(orderedpaths, numpaths, sizeof(ddataptr), (__compar_fn_t)compdelay);

    /*----------------------------------------------------*/
    /* Report on top <numReportPaths> maximum delay paths */
    /*----------------------------------------------------*/

    if (summarydir != NULL) {
        if (fsum != NULL) fclose(fsum);
	sprintf(summaryfile, "%s/pin_to_reg_max.log", summarydir);
        fsum = fopen(summaryfile, "w");
        if (fsum == NULL) {
	    fprintf(stderr, "Cannot open %s for writing\n", summaryfile);
	}
    }
    if (fsum)
	fprintf(fsum, "Vesta static timing analysis, "
			"pin-to-register and register-to-pin maximum timing\n");

    fprintf(stdout, "\nTop %d maximum delay paths:\n", (numpaths >= numReportPaths) ?
			numReportPaths : numpaths);
    if (fsum) fprintf(fsum, "\nTop %d maximum delay paths:\n",
			(numpaths >= numReportPaths) ?
			numReportPaths : numpaths);
    for (i = 0; ((i < numReportPaths) && (i < numpaths)); i++) {
        testddata = orderedpaths[i];
        for (testbt = testddata->backtrace; testbt->next; testbt = testbt->next);

        if (testddata->backtrace->receiver->refinst != NULL) {
            fprintf(stdout, "Path input pin %s to %s/%s delay %g ps\n",
                        testbt->receiver->refnet->name,
                        testddata->backtrace->receiver->refinst->name,
                        testddata->backtrace->receiver->refpin->name,
                        testddata->delay);
            if (fsum) fprintf(fsum, "Path input pin %s to %s/%s delay %g ps\n",
                        testbt->receiver->refnet->name,
                        testddata->backtrace->receiver->refinst->name,
                        testddata->backtrace->receiver->refpin->name,
                        testddata->delay);
        }
        else {
            fprintf(stdout, "Path input pin %s to output pin %s delay %g ps\n",
                        testbt->receiver->refnet->name,
                        testddata->backtrace->receiver->refnet->name,
                        testddata->delay);
            if (fsum) fprintf(fsum, "Path input pin %s to output pin %s delay %g ps\n",
                        testbt->receiver->refnet->name,
                        testddata->backtrace->receiver->refnet->name,
                        testddata->delay);
        }
        if (longFormat) print_path(testddata->backtrace, stdout);
        if (fsum) print_path(testddata->backtrace, fsum);

        if (testddata->backtrace->receiver->refinst != NULL) {
	    if (longFormat) {
		fprintf(stdout, "   setup at destination = %g\n", testddata->setup);
		fprintf(stdout, "\n");
	    }
	    if (fsum) {
		fprintf(fsum, "   setup at destination = %g\n", testddata->setup);
		fprintf(fsum, "\n");
	    }
	}
    }

    fprintf(stdout, "-----------------------------------------\n\n");
    if (fsum) fprintf(fsum, "-----------------------------------------\n\n");
    fflush(stdout);

    /*--------------------------------------------------*/
    /* Clean up the path list                           */
    /*--------------------------------------------------*/

    while (pathlist != NULL) {
        freeddata = pathlist;
        pathlist = pathlist->next;
        while (freeddata->backtrace != NULL) {
            freebt = freeddata->backtrace;
            freeddata->backtrace = freeddata->backtrace->next;
            freebt->refcnt--;
            if (freebt->refcnt == 0) free(freebt);
	    else break;
        }
        free(freeddata);
    }

    free(orderedpaths);

    for (testconn = inputlist; testconn; testconn = testconn->next) {
        testconn->tag = NULL;
        testconn->metric = 1E50;
    }

    /*--------------------------------------------------*/
    /* Now calculate minimum delay paths from inputs    */
    /*--------------------------------------------------*/

    reset_all(netlist, MINIMUM_TIME);
    numpaths = find_clock_to_term_paths(inputconnlist, &pathlist, netlist, MINIMUM_TIME);
    fprintf(stdout, "Number of paths analyzed:  %d\n", numpaths);
    fflush(stdout);

    find_clock_skews(pathlist, MINIMUM_TIME);

    /*--------------------------------------------------*/
    /* Collect paths into a non-linked array so that    */
    /* they can be sorted by delay time                 */
    /*--------------------------------------------------*/

    orderedpaths = (ddataptr *)malloc(numpaths * sizeof(ddataptr));

    i = 0;
    for (testddata = pathlist; testddata; testddata = testddata->next) {
       orderedpaths[i] = testddata;
       i++;
    }

    qsort(orderedpaths, numpaths, sizeof(ddataptr), (__compar_fn_t)compdelay);

    /*----------------------------------------------------*/
    /* Report on top <numReportPaths> minimum delay paths */
    /*----------------------------------------------------*/

    if (summarydir != NULL) {
        if (fsum != NULL) fclose(fsum);
	sprintf(summaryfile, "%s/pin_to_reg_min.log", summarydir);
        fsum = fopen(summaryfile, "w");
        if (fsum == NULL) {
	    fprintf(stderr, "Cannot open %s for writing\n", summaryfile);
	}
    }
    if (fsum)
	fprintf(fsum, "Vesta static timing analysis, "
			"pin-to-register and register-to-pin minimum timing\n");

    fprintf(stdout, "\nTop %d minimum delay paths:\n", (numpaths >= numReportPaths) ?
			numReportPaths : numpaths);
    if (fsum) fprintf(fsum, "\nTop %d minimum delay paths:\n",
			(numpaths >= numReportPaths) ?
			numReportPaths : numpaths);
    for (i = numpaths; (i > (numpaths - numReportPaths)) && (i > 0); i--) {
        testddata = orderedpaths[i - 1];
        for (testbt = testddata->backtrace; testbt->next; testbt = testbt->next);

        if (testddata->backtrace->receiver->refinst != NULL) {
            fprintf(stdout, "Path input pin %s to %s/%s delay %g ps\n",
                        testbt->receiver->refnet->name,
                        testddata->backtrace->receiver->refinst->name,
                        testddata->backtrace->receiver->refpin->name,
                        testddata->delay);
            if (fsum) fprintf(fsum, "Path input pin %s to %s/%s delay %g ps\n",
                        testbt->receiver->refnet->name,
                        testddata->backtrace->receiver->refinst->name,
                        testddata->backtrace->receiver->refpin->name,
                        testddata->delay);
        }
        else {
            fprintf(stdout, "Path input pin %s to output pin %s delay %g ps\n",
                        testbt->receiver->refnet->name,
                        testddata->backtrace->receiver->refnet->name,
                        testddata->delay);
            if (fsum) fprintf(fsum, "Path input pin %s to output pin %s delay %g ps\n",
                        testbt->receiver->refnet->name,
                        testddata->backtrace->receiver->refnet->name,
                        testddata->delay);
        }
        if (longFormat) print_path(testddata->backtrace, stdout);
        if (fsum) print_path(testddata->backtrace, fsum);

        if (testddata->backtrace->receiver->refinst != NULL) {
	    if (longFormat) {
		fprintf(stdout, "   hold at destination = %g\n", testddata->setup);
		fprintf(stdout, "\n");
	    }
	    if (fsum) {
		fprintf(fsum, "   hold at destination = %g\n", testddata->setup);
		fprintf(fsum, "\n");
	    }
	}
    }

    fprintf(stdout, "-----------------------------------------\n\n");
    if (fsum) fprintf(fsum, "-----------------------------------------\n\n");
    fflush(stdout);

    if (fsum != NULL) fclose(fsum);
    if (summaryfile != NULL) free(summaryfile);
    if (summarydir != NULL) free(summarydir);

    /*--------------------------------------------------*/
    /* Clean up the path list                           */
    /*--------------------------------------------------*/

    while (pathlist != NULL) {
        freeddata = pathlist;
        pathlist = pathlist->next;
        while (freeddata->backtrace != NULL) {
            freebt = freeddata->backtrace;
            freeddata->backtrace = freeddata->backtrace->next;
            freebt->refcnt--;
            if (freebt->refcnt == 0) free(freebt);
	    else break;
        }
        free(freeddata);
    }

    free(orderedpaths);

    return 0;
}
